supply-pulse
Live cross-ecosystem malicious package radar.
Public, read-only dashboard tracking malware advisories across npm, PyPI, RubyGems, Cargo, Maven, NuGet, Composer, Go and more — with download-volume blast radius scoring.
No sign-up, no auth, no telemetry. Refreshes every 30 minutes from public APIs.
Why it exists
After the March 31, 2026 Axios npm compromise — a North-Korea-nexus actor pushed [email protected] and [email protected] containing a hidden malicious dependency to a package with ~98M weekly downloads — supply-chain compromise has become the single largest npm risk vector. The GitHub Security Advisory database publishes ~14 new malware advisories per day.
There is currently no fast, public, sign-up-free dashboard that answers the platform-engineer's Monday-morning question:
"Did anything bad get published to npm/PyPI over the weekend that I should care about?"
supply-pulse fills that gap. One screen. Live. Public.
What it shows
- KPI strip: malware advisories in the last 24 h / 7 d / 30 d / total tracked.
- 30-day stacked bar timeline (filterable by ecosystem).
- Highest-blast-radius list — top 10 affected packages from the last 7 days, ranked by weekly download count.
- Live feed: paginated, filterable by ecosystem, severity, and free-text search across summary and package name.
- Per-row expansion: full GHSA / CVE id, severity, affected package list, link out to the GitHub Security Advisory.
Data sources
All public, no auth required. URLs and refresh frequency:
| Source | URL | Refresh |
|---|---|---|
| GitHub Security Advisories (malware only) | https://api.github.com/advisories?type=malware&per_page=100&sort=published&direction=desc | every 30 min, 3 pages (≈ 300 advisories) |
| npm registry weekly downloads | https://api.npmjs.org/downloads/point/last-week/<pkg> | every 6 h, top 150 recently-affected npm packages |
| PyPI weekly downloads | https://pypistats.org/api/packages/<pkg>/recent | every 6 h, top 50 recently-affected PyPI packages |
Setting the optional GITHUB_TOKEN env var raises the GitHub rate limit from 60 req/hr to 5000 req/hr. Without it, supply-pulse still works fine — 60 req/hr is more than enough for our cron.
No mock data, no synthetic numbers, no Math.random(). If a download count is unavailable for an ecosystem, it is stored as NULL and rendered as —.
Running locally
npm install
cp .env.example .env
node server.js
# → listening on http://0.0.0.0:4724/supply-pulse/
The first poll fires ~3 seconds after startup so the dashboard isn't empty on a cold deploy. All subsequent polls run on cron.
Endpoints
All public, all read-only.
| Method | Path | Notes |
|---|---|---|
| GET | /supply-pulse/ | Static SPA |
| GET | /supply-pulse/health | {status, advisories_count, last_fetch_at} |
| GET | /supply-pulse/api/stats | KPI summary + by-ecosystem totals |
| GET | /supply-pulse/api/feed | ?ecosystem=&hours=&limit=&offset=&severity=&q= |
| GET | /supply-pulse/api/recent | Last N advisories regardless of filter |
| GET | /supply-pulse/api/timeline | ?days=30&ecosystem= daily counts |
| GET | /supply-pulse/api/ecosystems | List of ecosystems with counts |
| GET | /supply-pulse/api/blast | Top advisories by weekly-downloads of affected package |
| GET | /supply-pulse/api/package/:ecosystem/:name | Per-package advisory history + downloads |
Tech stack
- Node.js 20+, Express 4
- better-sqlite3 (WAL mode), single file under
data/supply-pulse.db - node-cron, helmet, compression
- Vanilla JS SPA, dark theme; Chart.js loaded from
cdn.jsdelivr.net
License
MIT.
Built by the cowork R&D pipeline (Claude Opus 4.7).