Every time you run npm install, you're trusting someone else's code to run on your machine. Not eventually — right now. Postinstall hooks fire the second a package lands. No review, no prompt.
I built plum to change that. It's a CLI that downloads the package tarball into memory, reads the source, and scores it before anything touches your project.
I pointed it at the 20 most downloaded npm packages. The results are mostly reassuring — but a few are worth talking about.
How plum works
When you run plum axios, it:
- Fetches the
.tgzfrom npm into memory (never to disk) - Scans every
.jsfile for red flags — obfuscated eval, credential access, shell execution, outbound HTTP in install scripts - Checks CVEs against the exact resolved version
- Looks at maintainer age, publish recency, download count, and typosquatting distance
Instead of pass/fail, plum gives a 0–100 score. The same signal — like child_process access — means something very different in TypeScript's language server versus a brand-new package from an unknown maintainer.
The results
| Package | Downloads/wk | Score | Status |
|---|---|---|---|
| debug | 553M | 100/100 | ✅ SAFE |
| semver | 635M | 100/100 | ✅ SAFE |
| commander | 367M | 100/100 | ✅ SAFE |
| chalk | 410M | 100/100 | ✅ SAFE |
| dotenv | 119M | 100/100 | ✅ SAFE |
| cors | 49M | 100/100 | ✅ SAFE |
| jsonwebtoken | 40M | 100/100 | ✅ SAFE |
| socket.io | 12M | 100/100 | ✅ SAFE |
| lodash | 146M | 100/100 | ✅ SAFE |
| moment | 31M | 100/100 | ✅ SAFE |
| react | 125M | 90/100 | ✅ SAFE |
| express | 92M | 90/100 | ✅ SAFE |
| vue | 11M | 90/100 | ✅ SAFE |
| eslint | 126M | 80/100 | ✅ SAFE |
| uuid | 240M | 80/100 | ✅ SAFE |
| mongoose | 5M | 80/100 | ✅ SAFE |
| axios | 99M | 70/100 | ✅ SAFE |
| typescript | 181M | 70/100 | ✅ SAFE ⚠ |
| webpack | 44M | 75/100 | ✅ SAFE ⚠ |
| next | 34M | 55/100 | ⚠ RISKY |
20/20 had zero CVEs. 16 scored SAFE. 1 scored RISKY.
The interesting ones
Next.js — 55/100, RISKY
Next.js is not malicious. It's maintained by Vercel, one of the most trusted teams in JS. The score comes from three child_process references in helper scripts that detect your package manager and check if you're online. Totally legitimate.
But 55/100 is a conversation starter, not a verdict. A framework using shell access is something you should know about, even if it's expected. That's the point of a score — it surfaces information and lets you decide.
TypeScript & Webpack — flagged, still SAFE
Both got dinged for child_process. TypeScript's language server needs shell access. Webpack spawns child processes for parallel builds. Neither is surprising. Plum caught real signals and still scored them SAFE given the context.
Axios — 70, worth a look
Axios scored 70 despite being clean with no CVEs and 99M weekly downloads. There's likely a pattern match hitting unnecessarily in the tarball — I'm investigating. If a tool flags something, it should explain every deduction. That's on the roadmap.
Why this matters
I ran npm audit on all 20 packages too. Zero issues. Also correct.
The difference is what each tool checks. npm audit is a database lookup — it tells you if a known CVE exists for a version. That's valuable. But the supply chain attacks that actually caused damage — event-stream, ua-parser-js, node-ipc — were all zero-days. No CVE existed when developers installed them.
Behavior-based scanning doesn't replace CVE checking. It adds a layer that databases can't.
Try it
npm install -g plum-scanner
plum <any-package>
It's open source: github.com/rjcuff/plum
Built in Rust. Scans in under a second. No account, no API key.
Top comments (0)