Tuesday morning, a worm started publishing malware to npm under some of the most boring names in the registry. By the time researchers tallied it, the count was 2,234 poisoned versions across 444 packages, centered on keyv — a key-value caching library pulling roughly 127 million downloads a week — and its extended family: cacheable, flat-cache, file-entry-cache, cache-manager. These are not exotic packages. They are the gravel under half the JavaScript ecosystem. Wiz pegs the payload as a descendant of the Shai-Hulud malware family, named for the sandworm in Dune, presumably because big thing under the surface that eats you when you fall into a rhythm was already taken.
The mechanics are worth understanding even if you never touch Node. Attackers compromised the GitHub account of the maintainer. The poisoned packages carry a preinstall hook — a script that runs the moment you install, before your code runs, before your tests run, before anything. The hook downloads a standalone runtime, executes an obfuscated second stage, harvests cloud credentials, CI tokens, and npm tokens, then uses the stolen npm tokens to republish trojaned versions of whatever else those tokens can reach. That last step is the worm part. Every infected developer becomes a distributor.
Wednesday, instead of my actual job
My stack is JavaScript on Cloudflare Workers, which means my stack is npm, which means Wednesday's schedule was decided for me. I froze deploys. I grepped every lockfile — the ERP, the estimating platform, anything with a package.json — for the affected names. Two repos had cacheable-request in the dependency tree, both pinned to versions that predate the compromise, because lockfiles pin exact versions and I do not update dependencies recreationally. I rotated the tokens anyway. Then I added --ignore-scripts to every CI install, which I should have done years ago and which, statistically, so should you.
To be precise about the exposure: Workers do not run npm installs in production, so the runtime was never at risk. The build pipeline was. The laptop was. The place where the Stripe keys and the Twilio secrets live is an environment file on a machine that runs npm install several times a day, and that is exactly the machine this worm was written for. The perimeter is wherever the install happens.
The uncomfortable part is that this is the precise failure mode I keep designing against in my own software. Every AI-generated change that touches my systems goes through a review gate, because I do not execute unreviewed changes against payroll. And yet npm's default behavior is to execute unreviewed code from the internet, automatically, at install time, with my credentials, as a convenience feature. The vibe coding era makes it worse. Agents install dependencies with great enthusiasm and zero suspicion, and some of the poisoned packages reportedly planted hooks aimed at code editors and coding agents specifically. The attackers have noticed who does the installing now, the same way I noticed last month that the tooling changed faster than the habits around it.
A preinstall script is a subcontractor who starts work before you've met him, in a room you didn't know your house had.
And a word for the maintainer, who is having a worse week than any of us. One person, maintaining free infrastructure that 127 million weekly installs lean on, and the whole blast radius traces back to one compromised account. I run payroll software alone, so I do not get to feel superior about the bus factor. I get to feel identified. The main difference between us is that my customers pay me, and his mostly opened GitHub issues.
Rotate your tokens. Not because you found something — because finding something was never the part of this you were going to be good at.