← back to gallery

burst-radar

npm publish-velocity anomaly detector — Mini Shai-Hulud signature, surfaced from the public feed in minutes

dev-toolsnpmsupply-chainsecurityshai-huludanomaly-detectionregistrydependenciesburst
Open product ↗

burst-radar

npm publish-velocity anomaly detector — surfaces packages publishing versions at suspicious speed in real time, straight from the public npm _changes feed.

The Mini Shai-Hulud TeamPCP campaign pushed 300+ versions across 323 @antv/* packages in 22 minutes on May 19, 2026. By the time advisory databases caught up, the malicious tarballs had already shipped to thousands of npm install runs. burst-radar surfaces that signature in 2–5 minutes — before the CVE lands.

burst-radar is a pre-advisory tripwire. It does not classify packages as malicious; it raises a hand whenever a package's version-publish rate sharply exceeds its historical baseline.

Pair with shai-watch and supply-pulse for confirmed malicious-package data.

Live data sources

All endpoints are publicly documented npm registry APIs. No auth, no API keys, no mocks, no seeds, no fake data anywhere.

| Source | Endpoint | Frequency |
| --- | --- | --- |
| npm CouchDB _changes feed | https://replicate.npmjs.com/_changes | every 2 min |
| npm registry metadata | https://registry.npmjs.org/<package> | per-package, on demand |
| npm weekly downloads | https://api.npmjs.org/downloads/point/last-week/<package> | every 30 min for new bursts |

On cold start, the cursor is initialized from https://replicate.npmjs.com/ (update_seq) so burst-radar starts watching "from now". No historical backfill.

If the feed is unreachable (network issue, sandbox), the UI shows feed-lag clearly and serves whatever's in the local DB. Empty state is honest — no bursts are ever fabricated.

Burst detection

For each tracked package, burst-radar maintains rolling counters over three windows: 30 min, 6 h, 24 h. A burst fires when any of the following thresholds are crossed:

burst_score is the maximum ratio across all windows. impact_score = log10(weekly_downloads) × burst_score. A burst that newly introduces install / preinstall / postinstall scripts gets a visual flag in the UI.

Stack

HTTP endpoints (all under /burst-radar)

| Method | Path | Purpose |
| --- | --- | --- |
| GET | /health | liveness probe (auth-free) |
| GET | /api/summary | top-level counts + sparkline |
| GET | /api/bursts?since=&min_impact=&limit=&order= | filtered burst list |
| GET | /api/bursts/active | bursts in the last 6 h, unresolved |
| GET | /api/package/:name | package metadata + versions + bursts |
| GET | /api/package/:name/timeline?days=7 | publish timeline |
| GET | /api/search?q=foo | name search across tracked packages |
| GET | /api/stats/daily?days=30 | aggregate per-day series |
| GET | /api/feed | JSON feed of newest bursts |
| GET | /api/log | recent system log entries |

Local dev

npm install
cp .env.example .env
npm start
# → http://127.0.0.1:4886/burst-radar/

better-sqlite3 ships prebuilt binaries for arm64 macOS and Linux. If you hit a build issue inside a sandbox, the production target (Mac Mini / VPS arm64) has prebuilts available.

Deploy

DEPLOY_MANIFEST.json follows the RNDLAB orchestrator schema. The Mac watcher on ~/development/RNDLAB/rndlab-core/ picks it up via cowork-deploy-bridge/queue.jsonl and handles rsync, systemd unit creation, nginx route, and Playwright thumbnail.

What burst-radar is NOT

License

MIT