Stage-1 vertical slice: Pydantic Case model, SQLAlchemy Core persistence, URLhaus Scoutline fetcher, FastAPI/Jinja cockpit (cases list + detail), flat Typer CLI, Result[T, E] type module, structlog config. Architecture in docs/dossier.md; 12-fold style guide in docs/style.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9.6 KiB
Review — API-Eligible Cyber Threat Reporting & Escalation Platforms (Draft v1)
Reviewer: Claude (Opus 4.7, 1M context)
Review date: 2026-05-13
Document reviewed: waypoints.md (first draft)
Verdict: Strong bones. Tone-perfect for white-hat defensive work — machine-to-machine, no vigilante framing. Publishable as an internal whitepaper after the critical fixes below.
1. What's Already Solid
Don't change these — they're load-bearing and correct.
- Section 1.1 vs 1.2 split (normal vs imminent harm) — exactly the right hinge for routing decisions.
- Section 8 (never-submit list) — covers GDPR / exploitation amplification / credential leakage failure modes well.
- Section 9 normalized object — the right abstraction. Transform-to-target instead of N bespoke pipelines.
- Section 10 architecture sentence — the whole project on one line: Sensors → OpenCTI → TheHive/IRIS → routing engine → MISP + abuse APIs + CERT/AIS → sanitized public.
2. Critical Fixes (do these before this leaves draft)
2.1 Geography mismatch — CISA AIS at #1 is US-only
For European-focused work, MISP via CIRCL.lu (Luxembourg) or the ENISA CSIRTs Network is the workhorse. CISA AIS does not cover EU institutions.
Action: Swap priorities #1 ↔ #2 (MISP first, AIS second). Add a row for CERT-EU specifically for European institutions.
2.2 National CERTs are referenced generically but never named
The doc says "National CERT/CSIRT" everywhere but never resolves it to an actionable receiver.
Action: Add a small table after Section 1:
| Country | Receiver | Channel |
|---|---|---|
| DE | BSI / CERT-Bund | reports@cert-bund.de, MISP community |
| FR | ANSSI / CERT-FR | TAXII feed |
| UK | NCSC-UK | structured email + early-warning service |
| NL | NCSC-NL | MISP |
| ES | CCN-CERT, INCIBE-CERT | MISP |
| EU | CERT-EU, Europol EC3 | TLP-tagged MISP |
The routing engine should pick the right one based on victim country.
Note on Europol EC3: they handle criminal cases, not first-call technical sharing. Route through your national CERT first; EC3 receives via national channels for cross-border coordination.
2.3 Domain registrar abuse is missing from Section 1.3
Cloudflare is covered, but registrars (Namecheap, Tucows, GoDaddy, EURid for .eu, DENIC for .de) are often the faster takedown path.
Action: Add to the malicious-infrastructure flow: registrar abuse contact from WHOIS → registrar abuse API/email → registry as escalation.
2.4 Severity scale A|B|C|D|E is unusual and undefined
Either define it inline or replace with the standard low|medium|high|critical (CVSS-style) or NIS2 severity categories for EU consistency. Receivers will normalize anyway — but defining it lets the routing engine make automatic decisions.
2.5 Normalized object missing an actor block
You have victim but no actor. Add:
"actor": {
"name": "Adira",
"aliases": [],
"campaign": "",
"confidence": "A1|A2|B1|B2|C2|C3|D|E|F"
}
This field connects the doc to the project mission and lets the routing matrix differentiate actor-specific sightings from generic abuse reports.
(A1–F is the Admiralty Code, the de-facto CTI standard. If that's too much, fall back to low|medium|high.)
2.6 PII at submission time is a GDPR landmine
Section 9 has observables.emails: []. Submitting victim email addresses to AbuseIPDB or VirusTotal is a personal-data transfer under GDPR.
Action: Add a pre-submission sanitizer step that:
- Hashes / redacts emails to
local-part-hash@domainwhen destination is public - Strips PII from URLs (tokens, query params containing identifiers)
- Keeps raw originals only in
evidence.raw_evidence_location(internal-only storage)
This belongs in the doc before the normalized-object section, not as an afterthought.
3. High-Value Additions
3.1 TLP enforcement at the routing layer
Nothing in the current schema prevents TLP:RED data being routed to a TLP:CLEAR destination.
Action: Add a routing precondition: submission.tlp <= destination.max_tlp_allowed.
- CISA AIS rejects TLP:RED
- Cloudflare doesn't care
- Spamhaus has its own rules
- MISP communities each have their own ceiling
Encode the ceiling per destination in the routing matrix.
3.2 STIX 2.1 as the serialization
Right now the doc implies internal object → bespoke transform per API. Cheaper and more standard:
internal object → STIX 2.1 bundle → minor adapter per destination
MISP, OpenCTI, CISA AIS, and most CTI tools are STIX-native. One serializer beats thirteen, and you get free interop with anything that already speaks STIX.
3.3 Rate-limit budgets
Many of these APIs have strict limits:
- AbuseIPDB free tier: 1000 reports/day
- VirusTotal public API: 4 req/min
- Spamhaus: per-submitter quotas
- Cloudflare: per-account rate limits
Without a token-bucket per destination, high-confidence submissions get silently dropped during bursts.
Action: Add a destination_quota field to the routing matrix and an enforcement layer.
3.4 Feedback loop is missing
When you submit to URLhaus, you can poll for status. When you submit to MISP, you get sightings. When you submit to Cloudflare, you get a case number. These should flow back into your OpenCTI graph as evidence-of-effectiveness.
Without this, you're operating open-loop — you don't know which destinations actually act on your reports.
Action: Add a Section 11 "Receipt and Effectiveness Tracking" that defines:
- Per-destination receipt schema (case ID, ack timestamp, outcome status)
- Polling cadence per destination
- A success metric per destination type (takedowns confirmed, sightings count, classification adopted)
3.5 NoMoreRansom (NMR)
Ransomware.live is listed under monitoring, but if a decryptor research effort produces anything, NMR is the destination.
Action: Add to the routing matrix:
| Evidence type | First API destination | Second destination | Internal system |
|---|---|---|---|
| Ransomware decryptor evidence | NoMoreRansom (private channel) | Victim CERT chain | OpenCTI internal only |
NMR coordinates so victims can decrypt before the adversary sees the fix — never publish a working decryptor publicly first.
4. Nice-to-Have
4.1 Submitter identity & signing
- Register a stable submitter handle with MISP / MalwareBazaar / AbuseIPDB — not a personal account.
- Sign internal objects with a project PGP key before they leave the system.
- CIRCL and other major MISP communities weight trust by submitter history.
4.2 Audit log requirement
Every external submission writes an immutable row:
(timestamp, destination, payload_hash, submitter_identity, tlp, response_id, outcome)
Legal cover, debugging, and the feedback loop in 3.4 all need this.
4.3 NIS2 callout for critical-infra reporting
EU NIS2 mandates incident reporting from regulated entities within 24h of awareness. If detections involve essential/important entity sectors, the routing engine should flag NIS2 obligation regardless of receiver choice.
4.4 Section ordering
Sections 8 (data handling) and 9 (normalized object) are foundations, not appendices. Move them up to Sections 3–4. Currently a reader hits the platform list before knowing what not to send.
4.5 Confidence convention
low|medium|high is fine, but production CTI commonly uses the Admiralty Code (A1, B2, etc., describing source reliability × information credibility) or estimative language. Mention the convention even if you don't fully adopt it.
5. Implementation Notes (Blue48 Hookup)
This doc is the spec for two components in the agent stack:
report_writeragent outputs Section 9's normalized object as its canonical format.- A routing engine (extension of
report_writer, or a 7th agent) consumes that object, applies the matrix in Section 6, and fans out via API adapters.
Agents stop at "produce the normalized object." Human review reads it, decides "yes, ship this to MISP and Cloudflare," and clicks. The routing engine then runs the API calls, captures receipts, and feeds them back to OpenCTI.
5.1 Suggested initial adapters (Block G priority)
- MISP (PyMISP)
- AbuseIPDB
- URLhaus
- Cloudflare Abuse Reports
- urlscan.io
These five cover ~80% of common evidence types in the routing matrix.
5.2 Secrets handling
Every adapter needs API credentials. They must:
- Live in
.env(already excluded from image via.dockerignore) - Be passed at container runtime via
env_file, never baked into the image - Be rotatable on a schedule (the audit log in 4.2 helps prove non-overlap)
6. Summary
| Category | Count | Notes |
|---|---|---|
| Critical | 6 | Geography, CERT mapping, registrar abuse, severity scale, actor block, PII sanitizer |
| High-value | 5 | TLP enforcement, STIX 2.1, rate limits, feedback loop, NoMoreRansom |
| Nice-to-have | 5 | Signing, audit log, NIS2, ordering, Admiralty Code |
After the critical fixes, this is a publishable internal whitepaper and a clear spec for the routing engine. Good draft.