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 ofnpm installruns. 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:
c30 ≥ 5versions in 30 min (absolute floor — Mini Shai-Hulud signature)c360 ≥ 15versions in 6 hc1440 ≥ 30versions in 24 hc30 / baseline ≥ 50×(relative anomaly)
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
- Node.js 18+, Express 4, better-sqlite3 (WAL), node-cron, helmet, compression, morgan
- Vanilla JS SPA, dark theme, English labels
- No auth on any endpoint — every URL is public and inspectable
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
- Not a malware scanner. It looks at rates, not tarball contents.
- Not a substitute for
npm audit, OSV, or GitHub Advisory DB. - Not a confirmation that a burst is malicious. A coordinated monorepo release or
tsc-style cadence can trigger benign bursts. The UI labels every burst as a signal, not a verdict.
License
MIT