31 Commits

Author SHA1 Message Date
Rene Fichtmueller
ceb49d0ab6 merge: reconcile prod-snapshot-2026-07-16 into main
Merges the production-only changes (Izzy PDF export via puppeteer,
Adoption Tracker overlay, audit scripts, deploy/utility scripts) that
had been living as uncommitted edits directly on Erik since before the
2026-07-14 backend refactor, on top of current main.

4 real conflicts, resolved by keeping whichever side is actually
functional rather than picking a side mechanically:

- package.json / package-lock.json: trivial union, kept both the
  refactor's pg/playwright deps and the snapshot's puppeteer dep;
  lockfile regenerated via `npm install --package-lock-only` rather
  than hand-edited
- server.js top-of-file: additive, kept both the localDb require
  (backend refactor) and the puppeteer lazy-loader (PDF export)
- server.js /api/health aspa_adoption block: main referenced
  `aspaAdoptionHistory`, a variable that is never declared anywhere
  in this file (confirmed by grep) -- a pre-existing dead reference
  that would throw if this code path ever ran. Kept the snapshot's
  working `aspaAdoptionDailyHistory`/`roaStore.count` version, which
  matches the live /api/health output from production. Also fixed
  the identical bug in the neighboring .catch() fallback block a few
  lines down (same undefined variable, not part of the conflict
  itself -- pre-existing on main, same fix applied for consistency).
- server.js hijack-subscribe/webhooks section (186 lines): main's
  side was just a 2-line comment ("Migrated to
  src/features/hijack-subscribe/"). Kept the snapshot's actual
  working implementation instead, because server.js does not
  require() or import anything from src/features/ or dist/ anywhere
  -- confirmed by grep. The "migration" only ever happened on the
  src/features/ side; server.js's own HTTP routing was never wired
  up to call into it.

IMPORTANT CAVEAT, found while investigating the above and NOT fixed
here: server.js still has 13 other "// Migrated to src/features/..."
comments (bgp-communities, irr-audit, asset-expand, rpki-history,
aspath, looking-glass, ix-matrix, submarine-cables, global-infra,
hijack-alerts (Fastify), changelog, rib, prefix-changes) where the
same thing is true -- the route implementation was deleted from
server.js and never replaced with a working call into src/features/.
These weren't touched by this merge (git didn't flag them as
conflicts, because production's snapshot never touched those
sections either -- only main changed them, by deleting them). If
this server.js is ever deployed as the new production file as-is,
those 13 endpoints will silently 404 or fall through, exactly like
the hijack-subscribe one would have if I'd taken main's side. This
is a pre-existing gap in the 2026-07-14 refactor (PR #1), unrelated
to the Izzy/Adoption Tracker work, and needs a deliberate decision
(wire server.js to src/features/, or confirm those routes are meant
to live on a different process/port entirely) before this branch
should be treated as deployable.

`node --check server.js` passes. Not deployed to Erik as part of
this commit -- Erik's currently-live server.js/public/index.html
are untouched.
2026-07-16 08:54:48 +02:00
Rene Fichtmueller
2766872aad snapshot: preserve production-only changes (Izzy PDF export, adoption tracker, audit scripts)
Working tree was 3+ months ahead of git in uncommitted local edits, sitting
on a detached HEAD at d3611a8 (2026-04-09), never reconciled with main.
Committing as-is on its own branch, without touching main or the detached
HEAD, to stop these files existing only on this one server:

- public/index.html + server.js: PDF export add-on gated to client Izzy,
  full Adoption Tracker overlay (ASPA + IPv6 tabs) -- neither exists
  anywhere in git history on any branch until now
- audit/: daily/rotating audit scripts + email report sender, previously
  untracked
- scripts/: tunnel-cleanup.sh, refresh-peeringdb.sh, previously untracked
- deploy-from-scp.sh, webhook-subs.json, hijack-alerts.json,
  aspa-adoption-history.json: previously untracked runtime/deploy state
- public/public/: duplicate mirror of the HTML variants found alongside
  the real public/ dir, preserved as found

No reconciliation with main attempted here -- that needs a deliberate pass,
not a side effect of a backup commit.
2026-07-16 06:42:03 +00:00
Rene Fichtmueller
5c1cb1652b fix: correct ASPA RFC citation and broken submission instructions
Reapplied against the current main (post src/features/ refactor),
since the old aspa/ module was carried over unchanged and still had
both bugs. Also caught two spots the stale local clone never had:
public/index.html's title= attribute and the newer index-classic.html.

ASPA has no RFC yet, still draft-ietf-sidrops-aspa-profile (object
format) and draft-ietf-sidrops-aspa-verification (path validation),
both in WG Last Call. "RFC 9582" is the ROA profile RFC, unrelated.

The generated "submit via RIPE DB webupdates / auto-dbm@ripe.net"
instructions were wrong too. ASPA objects are signed RPKI objects
created through a RIR's hosted RPKI platform, not RPSL text pasted
into the whois database. Replaced generateASPAObject/
generateRipeDbTemplate with summarizeASPAObject (review summary) and
generateASPASubmissionGuide (per-RIR: status, where to actually
create the object, source link). aspa_generate now takes an optional
rir param instead of a RIPE-specific maintainer handle.

Also corrected an overprecise APNIC launch date: APNIC's own blog
doesn't give one, only a vague "since Nov 2025" for all three live
RIRs collectively, which doesn't match RIPE's and ARIN's own more
precise announcements.
2026-07-15 23:53:00 +02:00
Rene Fichtmueller
2ab48972c5 refactor: Replace external RPKI/BGP APIs with local PostgreSQL database queries
- Create local-db-client.js with consolidated database client module (11 functions)
- Refactor validateRPKIWithCache() to query local rpki_roas table (<10ms vs 1-2s external)
- Update /api/health endpoint to determine health from local DB statistics
- Update /api/prefix-detail endpoint to use async validateRPKIWithCache()
- Update /api/prefix-changes endpoint with RPKI status lookup from local DB
- Create /api/bgp endpoint with local BGP routes + threat intelligence lookup
- Add bgp_routes, rpki_roas, threat_intel statistics to health response
- Zero external API calls for RPKI/BGP validation queries

Impact: Sub-100ms latency for all lookups, 0 token spend on BGP/RPKI/threat intel

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-04-28 21:41:01 +02:00
Rene Fichtmueller
d3611a8169 ui: replace BMAC emoji with styled badge button 2026-04-09 21:58:17 +02:00
Rene Fichtmueller
a04cf91abb ui: make masthead meta links clearly clickable (underline + blue color + hover) 2026-04-09 21:50:19 +02:00
Rene Fichtmueller
98aa1c31a6 ui: increase BMAC coffee emoji size to 1rem 2026-04-09 21:48:29 +02:00
Rene Fichtmueller
89d25ed47e ui: add Blog + BMAC links to masthead meta 2026-04-09 21:31:25 +02:00
Rene Fichtmueller
272729a6dd ui: move GitHub + Changelog from nav to masthead meta — clean nav with only data sources 2026-04-09 21:24:08 +02:00
Rene Fichtmueller
388656093b ui: move GitHub + Changelog from nav to masthead meta — clean nav with only data sources 2026-04-09 21:22:56 +02:00
Rene Fichtmueller
c8761e9332 ui: fix score breakdown card — match newspaper design (no blue tint, sharp corners, correct border colors) 2026-04-09 20:51:26 +02:00
Rene Fichtmueller
2db994da7f fix: add missing renderResilienceScore + renderRouteLeak functions
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.
2026-04-09 20:23:27 +02:00
Rene Fichtmueller
969595b9b4 fix: eliminate 40-72s hangs from fetchJSONWithRetry + add frontend timeouts
Server:
- aspath: announced-prefixes 15s→5s, looking-glass 20s→6s (no retry)
- rpki-history: routing-history 20s→6s (no retry)
- looking-glass endpoint: 20s→6s (no retry)
- communities: bgp-state 12s→6s (no retry)
- checkHijacksForAsn: announced-prefixes 15s→6s (no retry)
- bgp-updates (pfxLoad): 25s→8s

All previously used fetchJSONWithRetry which silently retried on timeout:
timeout + 1s wait + timeout = up to 72s cold. Now single attempt, 5-6s cap.

Frontend:
- loadCommunities: add 8s AbortController
- loadIrrAudit: add 8s AbortController
- loadRpkiHistory: add 8s AbortController
- loadAspath: add 10s AbortController
- loadHijackMonitor: add 8s AbortController
2026-04-09 15:22:50 +02:00
Rene Fichtmueller
5b04fc663f fix: cap lookup/validate at ≤10s cold, fix infinite skeleton spinner
lookup:
- Remove WithRetry on Prefixes/Neighbours (8s+retry+8s=17s → 8s max)
- Add 9s hard cap inside timedFetch per source
- Visibility timeout 12s → 8s

validate:
- Phase 1 timeout 8s → 5s (prevents blocking Phase 2)
- Phase 2 per-check cap: 10s → 5s (total ≤10s for any ASN)
- rdns sample: 20 → 3 (was 20 concurrent RIPE Stat calls)
- rdns per-call timeout: 5s → 4s

Frontend doLookup:
- Add 15s AbortController on /api/lookup fetch
- Show 'timed out — try again' instead of infinite skeleton spinner
2026-04-09 08:52:28 +02:00
Rene Fichtmueller
35b89c05aa fix: eliminate hanging cards — ASPA/bgproutes/WHOIS/PeeringRec all responsive
- ASPA: 15min result cache + looking-glass timeout 3s (was 8s), hard cap 12s (was 18s)
- bgproutes: 15min result cache + 6s timeout on RIB POST (was no timeout), vpCache 1h
- WHOIS: 24h cache + RDAP fallback timeouts 3s (was 5s)
- Peering Recommendations: replace 20x full /api/lookup with new /api/quick-ix
- postJSON: add configurable timeout (was no timeout, caused indefinite hangs)
- Frontend: AbortController timeouts on all slow card fetches (ASPA/bgproutes/WHOIS/health)
- New /api/quick-ix endpoint: PeeringDB IX data + network name, 1h cached
2026-04-08 23:56:08 +02:00
Rene Fichtmueller
f1fe96132f fix: version strings all updated to v0.6.9 (masthead, footer, terminal) 2026-04-04 23:48:30 +02:00
Rene Fichtmueller
f6168f1329 feat: resilience score, route leak detection, data provenance, MCP server
- 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).
2026-04-04 23:46:36 +02:00
Rene Fichtmueller
9be247410c fix: IXP picker wrong data path + move Facilities card + IX capacity stat 2026-04-02 21:50:12 +00:00
Rene Fichtmueller
32bb279c1d feat: add RS column, contacts, timing panel, JSON export, city (v0.6.6) 2026-04-02 21:39:28 +00:00
Rene Fichtmueller
fae091801c feat: replace Leaflet map with MapLibre GL + global infrastructure overlays
- 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)
2026-03-29 08:37:55 +02:00
Rene Fichtmueller
036ca861ae fix: bgp.he.net scraper + peering recommendations
bgp.he.net scraper:
- Fixed prefix regex: "Prefixes Originated (v4): 147" format
- Fixed peer regex: "BGP Peers Observed (all): 274" format
- Added prefixes_all field
- AS6830: v4=147, v6=9, peers=274 (was all unavailable)
- Prefix cross-check now works: RIPE 151 vs HE 156 = 97% agreement

Peering Recommendations:
- Now filters out already-established peering sessions
- 3 categories: New Opportunities, Already Peering, No Shared IXP
- Uses BGP neighbour data to detect existing sessions
- Shows "Already peering with all top networks" when applicable
2026-03-28 02:32:50 +13:00
Rene Fichtmueller
f21a8bbba6 feat: Score Breakdown section + fix URL parsing crash
Dashboard: Added "Score Breakdown — Why X/100?" section showing:
- Per-check weight, earned points, and reason
- Total calculation with formula explanation
- Data source attribution
- "info" status excluded from scoring (e.g. MANRS API auth)

Security: try-catch around new URL() parser — malformed URLs from
scanner bots (XSS attempts) now return 400 instead of crashing server.
Was causing repeated crashes from automated vulnerability scanners.
2026-03-28 02:24:51 +13:00
Rene Fichtmueller
d1825fe327 fix: missing closing brace in renderNetworkMap broke all JS
renderNetworkMap() was missing its closing } after the setTimeout(50)
callback. This caused a SyntaxError that prevented the entire script
from parsing — doLookup was undefined, Lookup button did nothing.

Also added deploy.sh backup script on Erik (auto-backup before restart,
keeps last 20 versions of server.js + index.html).
2026-03-28 01:00:51 +13:00
Rene Fichtmueller
33d6a84d47 fix: map tiles + PeeringDB rate limit resilience
- Leaflet map: double requestAnimationFrame after display:none removal
  ensures container has real dimensions before L.map() init
- PeeringDB org cache: 24h disk cache (.pdb-org-cache.json) prevents
  hammering PeeringDB API on server restarts (was causing 175 restarts)
- Check HTTP status before JSON.parse on PDB responses
2026-03-27 23:31:32 +13:00
Rene Fichtmueller
f8784bbcec fix: Leaflet map tile rendering in collapsed containers
invalidateSize() + refitBounds after 200ms delay fixes tiles only loading
in top-left corner when map card was initially hidden
2026-03-27 23:19:57 +13:00
Rene Fichtmueller
9aeffda8d1 feat: interactive network footprint map with Leaflet.js
- Leaflet.js (CDN) with CartoDB Dark Matter tiles matching Tokyo Night theme
- Cyan markers: facility/datacenter locations with name + city popup
- Orange markers: IX presence with IX name + speed popup
- Purple connecting lines between facilities in the same country
- Coordinates from PeeringDB facility API (batch lookup, chunked)
- IX locations via ixfac association + facility geocoding
- Auto-fit bounds, graceful degradation if no coordinates
- Collapsible card, XSS-safe popups via DOM API
2026-03-27 11:28:14 +13:00
Rene Fichtmueller
13c5152bf9 feat: multi-source data validation with confidence scoring
- RPKI cross-check: Cloudflare RPKI feed + RIPE NCC Validator API (5 sample prefixes)
- Prefix cross-check: RIPE Stat vs bgp.he.net count comparison
- Neighbour cross-check: RIPE Stat vs bgp.he.net peer data
- Data Quality badge in dashboard (High/Medium/Low confidence)
- Hover tooltip: "Data Quality Report" with per-source agreement breakdown
- Added BETA tag to site header and version string (v0.5.0-beta)
- All UI text in English
2026-03-27 10:22:10 +13:00
Rene Fichtmueller
6fdda92757 fix: critical data accuracy fixes from NOG community feedback
RPKI Validation:
- Validate ALL prefixes (not sample of 10) using local Cloudflare RPKI feed
- Covers all 5 RIRs globally (RIPE, APNIC, ARIN, LACNIC, AFRINIC)
- Indexed ROA lookup (O(bucket) not O(824K)) for instant validation
- AS4739 now correctly shows 446/446 prefixes checked

ASPA Provider Detection:
- Only RIPE Stat "left" neighbours (verified upstreams) used as providers
- AS-path analysis used for frequency confirmation only, not as provider source
- Fixes false provider detection that included peers alongside upstreams

Multi-RIR Support:
- WHOIS/IRR queries all 5 RIR databases via RDAP in parallel
- RPSL validation checks RIPE + APNIC/ARIN/LACNIC/AFRINIC
- AS4739 (APNIC) now correctly found via rdap.apnic.net

Geolocation:
- Anycast/CDN networks (5+ facility countries or Content/NSP type) not flagged
- Only small networks with geo anomalies get warnings

Route Server Scoring:
- Networks with 20+ IX connections and no RS scored as "pass" (bilateral policy)
- Only small networks without RS get warnings

Error Handling:
- ASPA endpoints gracefully handle timeouts (show fallback instead of HTML parse error)
- Frontend checks Content-Type before JSON.parse

Reported by Philip Smith, Richard Steenbergen, Jared Mauch, Chris Malayter
2026-03-27 10:06:17 +13:00
Rene Fichtmueller
405bfd01c7 fix: resolve double ASN display in ASPA provider badges 2026-03-26 11:20:02 +13:00
Rene Fichtmueller
cdf21b9e8e feat: add RIPE Atlas probe integration to dashboard
- Query RIPE Atlas API for probes in the looked-up ASN
- Display probe count, connected/disconnected status, anchors
- Expandable probe detail table with links to atlas.ripe.net
- Connection ratio progress bar
- "Host a probe?" prompt for networks without Atlas presence
2026-03-26 11:14:41 +13:00
Rene Fichtmueller
fc58394555 feat: complete dashboard with ASPA, bgproutes.io, enhanced RPKI
- Full network intelligence dashboard (777-line HTML)
- ASPA Intelligence: provider detection, object generator, path analysis
- bgproutes.io integration: 3293 vantage points, RIB queries, ROV+ASPA status
- Enhanced RPKI: per-prefix validation, coverage percentage, expandable details
- Enhanced Compare: common upstreams, RPKI coverage comparison
- API endpoints: /api/lookup, /api/aspa, /api/bgproutes, /api/compare, /api/health
- All data sources queried in parallel for speed
- Tokyo Night dark theme, responsive, loading states
2026-03-26 10:23:44 +13:00