const { TIER1_ASNS } = require("./data/tier1-asns"); // Heuristic: detects suspicious routing relationships using RIPE Stat neighbour data. // NOT real-time. False positives possible for large networks with many Tier-1 relationships. // Confidence: MEDIUM — pattern-based, not path-level analysis. function computeRouteLeakDetection(upstreams, downstreams, peers) { const upstreamAsns = new Set(upstreams.map(n => n.asn)); const downstreamAsns = new Set(downstreams.map(n => n.asn)); const tier1Upstreams = upstreams.filter(n => TIER1_ASNS.has(n.asn)); const tier1Downstreams = downstreams.filter(n => TIER1_ASNS.has(n.asn)); const patterns = []; // Pattern A: Tier-1 appearing as BOTH upstream AND downstream → sandwich candidate const sandwich = tier1Upstreams.filter(n => downstreamAsns.has(n.asn)); sandwich.forEach(n => { patterns.push({ type: "sandwich_candidate", asn: n.asn, name: n.name, description: `AS${n.asn} (${n.name}) appears as both upstream and downstream — possible route leak vector`, }); }); // Pattern B: Tier-1 as downstream (re-originating routes to Tier-1s) tier1Downstreams.forEach(n => { if (!upstreamAsns.has(n.asn)) { patterns.push({ type: "tier1_downstream", asn: n.asn, name: n.name, description: `AS${n.asn} (${n.name}) is a downstream — unusual for a Tier-1, may indicate leaked routes`, }); } }); const detected = patterns.length > 0; return { detected, patterns, tier1_upstream_count: tier1Upstreams.length, tier1_downstream_count: tier1Downstreams.length, _provenance: { source: "RIPE Stat asn-neighbours", validation: "heuristic", confidence: "medium", note: "Pattern-based detection only. Not real-time (15-min RIPE RIS snapshot). False positives possible for large networks with legitimate Tier-1 relationships.", }, }; } module.exports = { computeRouteLeakDetection };