voidly

Stealth blackout detector: 458 candidate days where BGP held but the data plane didn't

Aryapour 2025 (arXiv 2507.14183) showed Iran can run a "stealth blackout" — keep BGP routes UP while throttling DNS/HTTP/HTTPS. Invisible to BGP-based IODA. We built a heuristic detector: ping-slash24 critical alerts ≥ 5 AND BGP relatively stable AND OONI blocking/interference corroboration. Found 458 candidate country-days (149 strong) — all already in our incidents table but with empty mechanism fields. The detector lets us back-classify opaque "Internet disruption" incidents as stealth-blackout-flavored.

#methodology#detection#stealth-blackout#iran#ioda#aryapour

Internet shutdowns are not always BGP withdrawals. Iran has repeatedly run what Aryapour (2025) calls a stealth blackout: leave BGP routes up so the world doesn't see a country fall off the map, but throttle DNS / HTTP / HTTPS below usable thresholds. Looks like a country with normal connectivity on the BGP control plane; users inside can't load anything.

Voidly's forecast uses IODA BGP alerts as a feature. That means stealth blackouts are systematically under-predicted: the BGP data they rely on says everything's fine.

The detection heuristic

  1. Rule 1: IODA ping-slash24 critical alerts ≥ 5 in a country-day (active-probing disruption)
  2. Rule 2: IODA bgp critical alerts < ping-slash24 × 0.5 (BGP relatively stable)
  3. Rule 3: Strength boosted by OONI blocking / interference / middlebox-detection ≥ 0.4 mean value

What we found (30-day window)

  • 458 candidate country-days total
  • 149 strong candidates (strength ≥ 0.7)
  • 191 unique countries flagged
  • Iran: 15/15 candidates strong with strength=1.0

Iran's pattern is textbook Aryapour: 2026-05-13 → 2026-05-18, six consecutive days with ping-slash24 critical alerts (84-221/day), BGP critical alerts at 0, and OONI blocking signal ~0.43-0.85 across 5-11 measurements per day.

The unexpected win: back-classification

Of the 458 candidates, 0 are novel — every single one is already in our incidents table. But they're all stored with mechanism = NULL, titled generically “Internet connectivity disruption in country X.”

The detector gives us a way to back-classify those opaque incidents as “stealth blackout / data-plane only” rather than treating them as ordinary outages. That's the unexpected research-grade win — not finding novel events, but characterizing the ones we already know about.

Top countries flagged

CountryCandidate days
VE Venezuela24
EG Egypt18
IN India15
IR Iran15
PA Panama12
TT Trinidad & Tobago12
SI Slovenia11
ZW Zimbabwe9
AO Angola8
BA Bosnia & Herzegovina8

Caveats — what the detector misses

  • One noisy day (2026-05-03) flagged 100+ countries simultaneously — almost certainly an IODA-side sensor blip, not a synchronized global stealth blackout. Need a global-blip filter before promoting.
  • No probe latency baseline per country-day: our probe_metrics table only has 1 day of history. Throttling at the probe layer is invisible to current sensors.
  • Probes concentrated outside censoring countries. Almost zero internal vantage in IR/CN/RU. Stealth detection from outside is structurally hard.
  • No throughput sampling. Pure-throttling stealth blackouts (no packet loss but reduced bandwidth) won't register on ping-slash24.

Recommended new sensors (long-term)

  • Internal vantage probes inside IR/CN/RU (high-risk co-op recruitment)
  • Per-domain throughput sampling (TCP segment-rate, TLS handshake duration)
  • Anycast latency telemetry from CDN partners (Cloudflare RAT, Fastly)
  • RIPE Atlas ping-anchor diff between control plane (BGP) and data plane (ICMP)

What ships today

GET /v1/sentinel/stealth-blackouts returns the candidate list with reasoning, evidence permalinks, matched existing incident IDs, and the documented data gaps. Filter by country, label (candidate / strong_candidate / weak), since, limit.

Marked research_notice: not promoted to production in the response. Forecast feature integration deferred until the global-blip filter lands.

Files: scripts/stealth-blackout-detector.py (heuristic + candidate generator), scripts/patch-stealth-blackout-endpoint.py (atomic Flask route patcher).

Raw data