A Worm Poisoned 2,236 npm Package Versions on Tuesday. The Part That Survives Your Cleanup Lives in .claude/ and .vscode/.
- Patrick Duggan
- 6 minutes ago
- 5 min read
On Tuesday 4 August a self-propagating worm began publishing malicious versions of the npm packages keyv and cacheable and their common dependencies. By the end of the day the count had reached 2,236 poisoned package versions. That number is the headline everywhere, and it is the least interesting thing about this campaign.
The interesting thing is where the payload was put. Alongside the ordinary npm compromise, the attackers planted executable content inside AI coding-agent and IDE configuration directories — the .claude and .vscode folders that sit in a repository root. Those files are not dependencies. They are not in package.json. They are not in your lockfile. Your dependency scanner does not read them, your SCA tool does not read them, and your remediation script almost certainly does not delete them. You can purge every bad version from your tree, get a clean audit, and still be carrying the part that runs.
What actually happened
The attackers took control of the GitHub account behind keyv, pushed two malicious files — setup.mjs and Math_Symbol.js — directly to the main branch, and cut new releases immediately. From there it self-propagated. Any maintainer who installed a compromised package had their npm publishing token stolen, and the attacker used that token to publish poisoned versions of that maintainer's own packages. That is a worm, not a campaign: the blast radius grows without the operator doing anything.
It worked. At least 868 additional packages across 1,381 versions were compromised through stolen tokens, reaching into packages published by Deliveroo, Picsart, Qlik, Ornikar and HubSync, with roughly a dozen organisations touched in total. The credit for the analysis belongs to the researchers who pulled this apart in near real time — Datadog Security Labs, Wiz, Aikido, Orca, Cloudsmith and safedep all published while it was still spreading, and the speed of that response is the reason the numbers above are as precise as they are.
We named this execution path on 29 May
On 29 May we published a post about an npm package that tried to exfiltrate Claude's working directory and leaked its own GitHub token doing it. The closing argument of that post was a prediction, and we will quote ourselves plainly because the date is the whole point: the next wave comes for Cursor. On 10 July we published a second piece on the same seam — GhostApproval, where a malicious repository's project_settings.json turned into a path to a developer's ~/.ssh directory, with the approval dialog reporting something entirely different from what was about to execute.
Both posts made the same argument. The AI coding agent introduced a new class of file that behaves like code, is committed to repositories, is fetched and trusted automatically, and is invisible to every tool built to inspect dependencies. The agent-config file is an execution path that the supply-chain security stack was not designed to see.
Nine weeks later, a worm used exactly that path at scale. We want to be careful about what that does and does not entitle us to claim. It does not mean we had indicators for this campaign before anyone else — we did not. It means the thesis was published, dated, and public well ahead of the event, which is a different and more modest kind of receipt. Publishing a prediction and then having it land is worth something. It is not worth pretending we saw this specific worm coming.
Who actually caught it, and what our feed is doing
Our malicious-package guardrail returns a block verdict for both keyv and cacheable right now. Any agent or CI pipeline calling our package check before an install gets told not to proceed. That is genuinely useful and it is available free, which matters to the shops that cannot buy a software composition analysis platform.
It is not our detection, and we are not going to dress it up as one. Both entries trace to OSV — advisory MAL-2026-11524 for keyv, published 4 August at 11:19 UTC, and MAL-2026-11963 for cacheable, published nine minutes later. OSV listed these the same day the attack ran, which is fast work by the people who maintain it. Our feed redistributes that list. Anyone else pulling OSV had identical coverage on the identical date, and the honest description of our contribution is distribution to people who would not otherwise have a pre-install guardrail wired in. The credit for the listing is OSV's. We are the delivery van, not the detective, and a threat-intel shop that blurs that line is selling you its ingest timestamp as insight.
Why the config-file payload is the part to act on
Think about how remediation normally goes. You find the advisory, you pin or bump the affected versions, you rebuild the lockfile, you re-run the scanner, and it comes back clean. Every one of those steps operates on the dependency graph. None of them touch a .claude or .vscode directory sitting in a repository you cloned, and the keyv repository still carried both directories on main as a separate execution path after the package versions were being cleaned up.
That asymmetry is the story. The dependency half of this attack is loud, well-tooled, and now largely handled. The agent-config half is quiet, untooled, and persists through a remediation that looks successful. If your organisation pulled any affected package on 4 August, or if any developer simply opened the keyv repository in VS Code or Claude Code, the dependency cleanup is not the end of the job.
What to do, in order
Check your dependency tree for the affected packages and versions first, because that part is well documented and the tooling works. Then rotate npm publishing tokens for every maintainer account in your organisation, because token theft is the propagation mechanism and a token stolen on Tuesday is still valid until you revoke it. Then — and this is the step nobody's runbook currently includes — go and look at the .claude, .vscode and equivalent agent-configuration directories in every repository cloned or updated in that window, and read them with your own eyes rather than a scanner.
Longer term, the lesson is a boring governance one. Agent-configuration files should be treated as executable content in code review, because that is what they functionally are. They should be diffed on pull requests with the same attention as a build script. If your review process waves through a change to .claude/settings.json because it is "just config," you have an unguarded execution path, and this week demonstrated that attackers have noticed.
The honest accounting
We predicted the vector and published it twice with dates on it. We did not detect this campaign, OSV did, and our guardrail is redistribution of their work. Both of those things are true at once, and a reader is entitled to have them separated rather than blended into a cleaner-sounding story.
The part we would push hardest on is the least self-serving: the config-file execution path is still not covered by the tools most teams rely on, and it will be used again before it is. We guarantee five percent of what we publish is wrong. We would rather that five percent live in a prediction we made too early than in a claim we made too confidently about who caught what.
Her name was Renee Nicole Good.
His name was Alex Jeffery Pretti.
