- Common IXPs table (both ASNs present — direct peering possible)
- Shows IXP name, city, port speeds for each ASN
- Shared colocation facilities table (datacenter co-presence)
- Expansion opportunities: IXPs where one ASN is present but the other isn't
- Lookup data fetched in parallel with validate/aspa for zero extra latency
- Comparison PDF now 5 pages including Peering Intelligence page
- Persistent JSON history (DATA_DIR/aspa-adoption-history.json, up to 365 days)
- Snapshots recorded on every RPKI feed refresh (~4h) + startup
- Coverage metric: % of Atlas-visible ASNs (4696+) with ASPA objects in RPKI
- GET /aspa-adoption → interactive Chart.js dashboard (MAGATAMA indigo theme)
- Current coverage %, ASPA object count, 7-day delta, 90-day forecast
- 30-day trend line chart + ASPA object bar chart
- GET /api/aspa-adoption-stats?period=7d|30d|90d|1y → JSON trend + linear forecast
- GET /api/aspa-adoption-stats/export?format=csv|json&period=30d → CSV/JSON download
- Integrates with existing RPKI feed pipeline (no new external dependencies)
- Fixes health endpoint to use new aspaAdoptionDailyHistory structure
- GET /api/export/pdf?asn=X&format=report|executive|technical
→ Generates multi-page PDF with cover, score ring, health checks, RPKI,
BGP topology, recommendations — MAGATAMA CI styling (indigo #6366f1)
- GET /api/export/pdf/compare?asn1=X&asn2=Y
→ Side-by-side comparison PDF with score rings, metrics table, head-to-head
health check grid
- 5-minute in-memory cache (X-Cache: HIT on second request)
- Lazy puppeteer browser pool (launched on first request, reused)
- Graceful fallback: returns 503 JSON if puppeteer unavailable
- ~300KB PDF, 4 pages, ~12s first render / 8ms cached
- 6 new REST endpoints: POST/GET/DELETE /api/webhooks, POST /api/webhooks/:id/test, GET/POST /api/hijacks
- HMAC-SHA256 signed webhook payloads (X-PeerCortex-Signature header)
- Exponential backoff retry: 2^attempt * 1000ms, up to 5 attempts
- 6-hour deduplication window per (ASN, severity) to prevent alert storms
- Severity classification: CRITICAL/HIGH/MEDIUM/LOW based on unexpected/missing ASNs + RPKI invalid
- DATA_DIR pattern for portable file storage (dev vs /opt/peercortex-app on Erik)
- Backwards-compatible: legacy /api/hijack-subscribe + /api/hijack-alerts routes kept
- Fix: 0-prefix cache results now use 90s TTL instead of 15min to prevent stale data
When RIPE Stat briefly returns empty prefixes, the validateResultCache
was storing this bad result for 15 minutes. Now 0-prefix results expire
after 90 seconds so the next request gets a fresh fetch.
Adds optional ttlMs parameter to resultCacheSet/resultCacheGet for
per-entry TTL control.
These were called in doLookup but never defined anywhere, causing:
'Error: renderResilienceScore is not defined'
This JS error aborted the entire render pipeline after the lookup
completed — meaning WHOIS, health report, ASPA, bgproutes all never
loaded because the catch block fired instead.
Also added AbortController timeouts to all 5 new feature card loaders.
- aspa/verify: 15min result cache, looking-glass 3s timeout (was 20s default), 5→3 prefixes
- fetchJSON default timeout: 20s→8s prevents all uncached RIPE Stat calls from waiting 20s
- All cards now respond in <1s on cold call (ASPA 200ms, verify 170ms, validate 820ms, WHOIS 50ms)
- bgproutes still 4s cold (bgproutes.io API latency, cached after first call)
Adds real-time local BGP RIB data as a complement to external APIs
(RIPE Stat, bgproutes.io) which have rate limits and 15min+ delays.
- bio-rd-client.js: gRPC client for bio-routing/bio-rd RIS service
- LPM, Get, GetLonger, GetRouters, DumpRIB, ObserveRIB methods
- IPv4/IPv6 encoding as uint64 pair (bio.net format)
- Full BGP path decode: AS paths, communities, large communities
- Graceful fallback if RIS unavailable (null/empty returns)
- protos/: bio-rd proto definitions (ris, bgp, session, route, net)
- server.js: three new endpoints + WebSocket stream
- GET /api/rib/prefix — LPM + more-specifics via GetLonger
- GET /api/rib/routers — list BMP-monitored routers
- GET /api/rib/dump — full RIB dump with ASN filter + limit
- WS /ws/rib — live ObserveRIB stream (add/withdraw events)
- package.json: @grpc/grpc-js + @grpc/proto-loader dependencies
- Resilience Score (1-10): weighted 4-factor model (transit diversity 30%,
peering breadth 25%, IXP presence 20%, path redundancy 25%), hard cap at
5.0 on single transit provider. Confidence: HIGH (cross-validated data).
- Route Leak Detection: heuristic Tier-1 sandwich/downstream pattern check.
Confidence: MEDIUM — pattern-based, not real-time, false positives flagged.
- Data Provenance System: every API response field includes source, validation
method and confidence level. UI shows green/orange provenance badges.
- MCP Server: exposes PeerCortex as Claude Desktop/Code tools (lookup_asn,
compare_networks, get_health_report, search_network, get_resilience_score).
For ASNs with no PeeringDB entry and no RIPE Stat holder (e.g. reserved
or unannounced ASNs), extract name from bgp.he.net page title and
country code from the /country/XX href. Eliminates the last 2 CRITICAL
audit failures (AS34465 → 'RIPE NCC ASN block'/GB, AS59947 → 'LLHOST
INC. SRL'/RO). Audit result: 80/82 PERFECT, 0 CRITICAL. v0.6.8.
- Install better-sqlite3 for zero-latency local queries
- queryPeeringDBLocal() handles all major PDB API paths locally:
/net?asn=X, /netixlan, /netfac, /fac?id__in=, /ixfac, /ix, /ixlan
- fetchPeeringDB() now tries local SQLite first, falls back to live API
- Eliminates rate limits and reduces P99 response times dramatically
- Local DB synced daily at 03:48 via peeringdb-py cron on Erik
- Graceful fallback: if SQLite missing/corrupt, live API used transparently
- MANRS: replace broken Observatory API with public participants page scraping
(www.manrs.org/netops/participants/), 24h cache, returns pass/fail with member count
- /api/validate: add 'relationships' field (upstreams/downstreams/top_peers)
sourced from RIPE Stat asn-neighbours, no extra API calls needed
- /api/relationships?asn=X: new dedicated endpoint with resolved AS names,
full upstream/downstream/peer lists sorted by power score, 10min cache
- editorial: rebrand 'The ASN Newspaper' → 'The ASN News' across index-editorial.html
Root cause of neighbour=0 for large carriers (AS9002, AS3491, AS12956):
1. RIPE Stat asn-neighbours returns 5000+ entries for Tier-1 carriers,
exceeding the 30s timeout → fetchJSON returns null
2. null was cached in ripeStatCache for 15 minutes (the endpoint TTL)
3. All subsequent requests hit the null cache → perpetual 0 neighbours
Fixes:
- Never cache null results in ripeStatCache (only successful responses)
- Never persist null entries to disk cache
- Increase RIPE Stat timeout from 30s to 45s for prefix/neighbour queries
- Increase RIPE Stat semaphore from 10 to 15 concurrent requests
Verified: AS9002 up=146 down=2702, AS3491 up=90 down=710
The audit script was flooding RIPE Stat and PeeringDB with unthrottled
parallel requests, causing 429 rate-limits that resulted in auth=0
false negatives (inflating the failure count).
Changes:
- Added threading.Semaphore for RIPE Stat (max 3) and PeeringDB (max 2)
- Added retry logic to _fetch_ripe (was fire-and-forget)
- Increased PDB retries from 2 to 3 with longer backoff (2s, 4s, 6s)
- Increased ASN stagger from 2s to 3s
Results: Accuracy 84% -> 87% (trend: 77% -> 87%, +10%)
- Phase 1: Parse ~400k ROAs from Cloudflare RPKI feed into local store
Eliminates ALL per-prefix RIPE Stat API calls (was 2000+ per lookup)
Binary search validation in <0.1ms instead of 1-20s HTTP roundtrip
Disk persistence (.roa-cache.json) for fast restart
- Phase 2: PeeringDB source cache (L2) for net/netixlan/netfac
6h TTL with LRU eviction (max 5000 entries per type)
Disk persistence (.pdb-source-cache.json) every 30min + SIGTERM
- Phase 3: RIPE Stat semaphore (max 10 concurrent) + response cache
Endpoint-specific TTLs (15min-24h based on change rate)
Max 2000 cached responses, disk persistence
- Phase 4: Extended /api/health with cache status, ASPA adoption metrics
Version bump to 0.6.0
Jittered refresh timers to prevent thundering herd
Graceful shutdown saves all caches
Expected: Audit accuracy 82% -> 95%+, lookup time 90s -> <8s
- index-editorial.html: floating \$_ terminal button (bottom-right)
- macOS-style title bar (traffic light dots), backdrop blur 18px
- Guided wizard: category → message → name → submit
- POST /api/feedback with ASN context auto-filled
- Safe DOM output builder (no innerHTML on user data)
- server.js: feedback API endpoints
- POST /api/feedback — stores entries to feedback.json
- GET /api/feedback?token=... — admin read (token-protected)
- OPTIONS preflight for CORS
- FEEDBACK_TOKEN + FEEDBACK_FILE constants from .env
- Host routing: shell.peercortex.org → shell.html
- public/shell.html: full-screen admin terminal
- login command → token auth via API
- list / list [category] — tabular overview
- show <n> — full entry detail
- stats — bar chart by category + top ASNs
- export — JSON file download
- refresh, logout, clear, help
- Upgrade from Leaflet to MapLibre GL JS 4.7.1 with OpenFreeMap dark base
- Add submarine cable layer (TeleGeography via /api/submarine-cables proxy, 24h cache)
- Add global datacenter layer (PeeringDB all facilities via /api/global-infra proxy)
- Layer toggles: ASN PoPs | Submarine Cables | Global Datacenters
- Dark-themed popup styling matching PeerCortex UI
- Server-side caching for both new data sources (24h TTL)