BGP Hijack & Routing-Hygiene Audit
Routing security you can act on. This grades a network on the conflicts and gaps that make route hijacking possible, then traces any conflict to the specific domains and organisations exposed on the affected blocks. An adversarial audit — not just a profile — for finding where a hijack could hurt and who it would hit.
The alert
A routing advisory names a prefix inside AS13335, and the hygiene scorecard for that network comes back total_prefixes 50 · prefixes_with_conflicts 0 · prefixes_with_roa 0. Zero conflicts reads as clean and zero ROAs reads as a network running no RPKI at all — one of those is true and the other is the opposite of true.
The question
Do I sign this network off as hijack-resistant, and what would tell me I am wrong?
Not "how many conflicts does it have". The conflict count is one column of the answer, and on this network it is the column that is correct and uninformative while the column beside it is simply false.
What this cannot answer
- The rest of the network. The scorecard grades fifty announced prefixes. AS13335 announces 5,258, and all five of its RPKI-invalid announcements sit outside that fifty — the audit's only actionable rows are the ones the grade never reaches.
- Whether a competing origin is hostile.
CONFLICTS_WITHholds a prefix's whole origin set, the announcing ASN included: all 7,174 prefixes carrying the edge list their own origin among the conflicts. No step returns the conflicting ASN'sorgNameeither, so the organisation behind a conflict is not in the output. - Whether a hijack ever happened. These are current-state routing rows. Origin over time is whisper.history(), a separate call.
- A coverage-qualified verdict.
whisper.assess(["AS13335"])returnslabel: unknown,band: UNKNOWN,coverage: no-data. Read Coverage before reading that as reassurance. - Which ROAs are decaying. Every one of the 4,984 ROAs authorizing this ASN expires within six days. That is the registries' reissue cadence rather than rot, so sorting by expiry ranks nothing.
What do we know
No step here calls explain() or whisper.assess, so there is no score to lean on and none to argue with. Asked directly, whisper.assess answers no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. for the ASN: a populated row stating the absence, which is what a real absence looks like.
What is known is routing state. AS13335 announces 5,258 prefixes, isMoas is true on none of them, and no CONFLICTS_WITH edge leaves any of them. That zero is checked against the plane rather than trusted — the graph holds 7,273 MOAS prefixes and 14,942 CONFLICTS_WITH edges, so the edge type is live and the zero is a fact about this network. Zero rows is never a verdict until the plane has been asked whether it is empty.
Why
- 01Announced prefixes & conflicts. A CONFLICTS_WITH origin is a MOAS conflict — the hijack signal.
- 02RPKI coverage. An uncovered prefix can be hijacked without RPKI-invalid rejection.
- 03Exposed hostnames. The blast radius — which domains are exposed if a prefix is hijacked.
- 04Routing-hygiene scorecard. One headline row grading the ASN: how many announced prefixes it has, how many are in MOAS conflict, and how many carry an ROA.
- 05ROA inventory (expiry & looseness). The ground-truth ROA set authorizing this ASN, each with days until it expires and how loose its maxLength is — the authoritative RPKI picture the anchor's name-match coverage check misses.
- 06Imminently expiring ROAs (90 days). ROAs lapsing within 90 days — when they expire, RPKI-valid routes silently become RPKI-unknown, so these are the top remediation priority.
- 07Over-permissive maxLength ROAs. ROAs whose maxLength sits far above the prefix length leave room for forged more-specific sub-prefix hijacks while still reading as valid — a gap coverage checks never catch.
- 08ROAs by RIR trust anchor. Buckets the ASN's ROAs by issuing RIR with the soonest expiry in each, so remediation can be routed to the right registry portal.
- 09MOAS conflicts for a prefix input. When the indicator is a prefix rather than an ASN, this lists the competing origin ASNs announcing it — a direct hijack signal.
The pivot the score did not suggest
Every prefix in the window reads uncovered, 1.1.1.0/24 among them — and 1.1.1.0/24 has a plainly visible ROA (apnic anchor, maxLength 24, authorizing AS13335). The coverage question never needed that join. RPKI validation state is already a property of the ANNOUNCED_PREFIX the traversal is standing on.
Read it there and the same fifty prefixes return 20 valid and 30 not-found, in 1 ms against the step's 1,152. Read it across all 5,258 and the audit finally has a finding: 5,116 valid, 137 not-found, 5 invalid. Three of the five are announced more specifically than their own ROA's maxLength permits; two carry roaAsn: 0, meaning no ROA authorizes any origin for that space. 103.21.244.0/24 is the second kind — single-origin AS13335, 27 addresses, 327 hostnames on them including levebank.com.br and levecrypto.com.br. That is the blast radius this workflow exists to produce, on a row it never reaches.
Falsify it
Re-ask the coverage question in the staged form: collect the window's prefix names, UNWIND them, anchor MATCH (p:PREFIX {name: n}), then OPTIONAL MATCH the ROA onto it. That returns 14 covered of 50 in 21 ms where the composed form returned 0 in 1,152 ms.
Three forms, three answers — 0, 14, 20 — and the six prefixes between 14 and 20 explain the page. 43.230.112.0/24 is one of them: a ROA node exists with prefix 43.230.112.0/24, maxLength 24, apnic anchor, reachable from the ASN by ROA_AUTHORIZES_ORIGIN, and carrying no ROA_AUTHORIZES_PREFIX edge at all.
So count the inventory both ways. Through ROA_AUTHORIZES_ORIGIN alone, 4,984 ROAs. Adding the prefix join, 3,765. 1,219 ROAs — a quarter of this network's RPKI — have no prefix edge to join through, and every ROA step joins through it. Read r.prefix instead and over-permissive maxLength goes from 17 to 135, its IPv4 half from 2 to 8: 104.28.0.0/16 authorized down to maxLength 24, 185.54.80.0/22 down to 32. The anchored forms are on BGP & routing.
The conclusion
AS13335 is clean on multi-origin conflict and not clean on RPKI, and the scorecard says the reverse of both. Nothing it announces is in MOAS conflict, across all 5,258 prefixes rather than across fifty. Five announcements are RPKI-invalid, and 1,219 of its 4,984 ROAs are invisible to every step that reports on them. Grade the network from ap.rpkiStatus over the full prefix set, not from the scorecard row.
Four things would overturn that, each a query rather than an opinion:
- a
CONFLICTS_WITHedge appearing on any AS13335 prefix — the registry record notes MOAS data stays sparse until the next route-collector refresh, so re-run before quoting the zero; ROA_AUTHORIZES_PREFIXedges backfilling, which would close the 4,984-against-3,765 gap and make the ROA steps truthful without a line of their Cypher changing;ap.rpkiStatusdisagreeing with an external RPKI validator on the five invalid rows;- any of the five invalid prefixes turning out to have a second origin, which is where this stops being hygiene and starts being an incident.
What this cost
9 named steps across the BGP, RPKI, DNS layers. The runner reports what each step returned, row by row, so the evidence and what it took to get it arrive together rather than as one number at the end.
How it uses the graph
Traverses the BGP, RPKI, DNS layers of the graph, in 9 steps:
Reading the ASN's announced prefixes and any competing origins…
Checking RPKI ROA coverage per prefix…
Mapping the hostnames sitting on those prefixes…
Routing-hygiene scorecard…
ROA inventory (expiry & looseness)…
Imminently expiring ROAs (90 days)…
Over-permissive maxLength ROAs…
ROAs by RIR trust anchor…
MOAS conflicts for a prefix input…