What Happened
On August 4, 2026, an attacker compromised the GitHub account of the maintainer behind keyv, a key-value storage library with roughly 127–150 million weekly npm downloads. That same maintainer also owns cache-manager, cacheable-request, @cacheable/utils, flat-cache, cacheable, file-entry-cache, @cacheable/memory, and @cacheable/node-cache — a family of caching utilities that together account for well over 500 million weekly downloads.
The attacker used that access to publish malicious versions across the family, and the malware then used any npm publish tokens it could steal to propagate itself into other, unrelated packages — worm behavior, not a single poisoned release. Reported numbers vary by source (some put it at 400+ packages, others over 2,200 versions across more than a dozen organizations), and no single source has published a complete, independently verified list. What's consistent across every report: this is large, it started August 4, and it is still being actively tracked as of this writing.
How It Actually Works
The malicious versions add a preinstall lifecycle script — meaning it runs automatically the moment npm install starts, before your application code, your test suite, or any conventional security scan gets a chance to look at anything.
package.json
{
"scripts": {
"preinstall": "node setup.mjs"
}
}
That script launches a heavily obfuscated payload (commonly named setup.mjs, with a second-stage file seen as Math_Symbol.js and later math_init.js), which downloads a Bun runtime and executes further obfuscated code. From there it:
- Harvests npm, GitHub, AWS, and HashiCorp Vault credentials reachable from the machine
- Exfiltrates them to an external domain (
npm-cache[.]comhas been reported) and to attacker-controlled GitHub repositories - Uses any stolen npm publish tokens to push malicious versions to other packages that account controls — this is the self-propagation step that makes it a worm rather than a one-off compromise
- Looks up its command-and-control domain via an Ethereum smart contract, so the operator can rotate infrastructure without changing the payload itself
One detail worth knowing if you use AI coding assistants: multiple researchers reported the payload also planting hooks targeting Claude Code and VS Code, specifically to persist inside developer tooling rather than just the CI pipeline.
Confirmed Bad Versions (Check Your Lockfile Against These)
keyv → 6.0.0
flat-cache → 6.1.24
file-entry-cache → 11.1.6
cache-manager, cacheable-request, @cacheable/utils → confirmed malicious, specific
version numbers vary by report — verify against your lockfile
cacheable, file-entry-cache, @cacheable/memory, and @cacheable/node-cache are confirmed as affected by the same maintainer compromise, but at the time of writing not every source has published a specific bad version number for each — treat any version of these published after August 4, 2026 as suspect until you've confirmed otherwise.
npm has since restored flat-cache@6.1.23 as a confirmed-clean version to roll back to. As of the most recent reporting, no confirmed-clean version had yet been published for file-entry-cache — check current advisories before picking a version to pin to.
Check If You're Affected
Run this in every project (and every CI config) that might touch these packages:
npm ls keyv flat-cache file-entry-cache cacheable-request cacheable cache-manager @cacheable/utils @cacheable/memory @cacheable/node-cache --all
This alone isn't enough — a bad version can still be sitting in your lockfile even if npm ls resolves something else locally right now. Also:
# Search your lockfile directly for the known-bad versions
grep -E "keyv\"?:\s*\"?6\.0\.0|flat-cache\"?:\s*\"?6\.1\.24|file-entry-cache\"?:\s*\"?11\.1\.6" package-lock.json
And check CI run logs for any install step between August 4, 2026 and whenever you patch, since a CI runner that pulled a bad version already executed the payload — a clean lockfile today doesn't undo that.
Indicators a version has been tampered with, even before checking version numbers against a list:
- A
preinstall(or unusualpostinstall) script that wasn't there in a previous release - Files named
setup.mjs,Math_Symbol.js, ormath_init.jsin the package - A "patch" release with no corresponding commit, pull request, or tag in the package's public source repository — several of the malicious releases were published straight to the registry with no matching source history at all