Nobody audits a caching library.
That is the lesson of 4 August 2026, when an attacker took over the GitHub account of Jared Wray — the maintainer behind keyv and cacheable — and published malicious versions of ten widely used npm caching packages. Within hours, a self-propagating worm had reached 444 package names across 2,234 poisoned versions.
The combined install volume of the affected ecosystem exceeds two billion downloads per month.
Which packages, and why the numbers are so large
The directly compromised set was small — roughly nine to ten packages owned by the one account. Socket's analysis lists keyv, cacheable, cacheable-request, cache-manager, flat-cache, file-entry-cache, @cacheable/utils, @cacheable/memory, and @cacheable/node-cache.
Look at the monthly download figures and the blast radius explains itself:
| Package | Approx. monthly downloads |
|---|---|
flat-cache | ~565M |
file-entry-cache | ~557M |
keyv | ~127M weekly |
cacheable | ~29M |
None of these is a package you chose. flat-cache and file-entry-cache arrive underneath ESLint. keyv arrives underneath cacheable-request, which arrives underneath got, which arrives underneath half your tooling. They are third- and fourth-order transitive dependencies in the build graph of projects that have never heard of them.
That is the structural point. The packages with the highest install counts are rarely the packages anyone evaluates. Your dependency review process, if you have one, looks at the direct dependency you added last sprint. It does not look at the caching layer four hops down that ESLint pulled in.
What the payload did
The mechanics, per Chainguard's write-up of what it calls the "Mini" Shai-Hulud campaign and Snyk's teardown:
- A malicious
preinstallhook fires during installation. - The hook downloads a standalone Bun runtime.
- Bun executes an obfuscated second stage.
- The second stage harvests npm tokens, GitHub tokens, AWS credentials, HashiCorp Vault secrets, SSH keys, and CI/CD environment secrets.
- Using any stolen npm token with publish rights, it republishes trojanized versions of every other package that token can reach.
Step five is the whole story. Steps one through four are a credential stealer, which is ordinary. Step five is what turned nine packages into 444 in a single afternoon.
Why worm propagation breaks incident response
Conventional supply chain IR assumes a fixed target list. You learn that package X version 1.2.3 is malicious, you search your lockfiles for it, you remediate, you are done. The list does not change while you work.
A worm invalidates that assumption in three ways:
The affected list grows during your investigation. The set you triaged at 14:00 is not the set at 16:00. Any remediation scoped to a snapshot is stale on arrival.
Victims become sources. A developer or CI runner in your own organisation with a valid npm publish token becomes a propagation node. Your exposure is not only "did we install a bad package" but "did a token of ours get used to publish one."
The credential blast radius is wider than the install blast radius. Every environment that ran the preinstall hook leaked whatever was reachable — and CI runners typically hold far more than a laptop does.
That third point is where most of the real damage sits. The install is the event; the credentials are the consequence.
Attribution and the pattern behind it
The campaign has been attributed to TeamPCP, deploying a descendant of the Shai-Hulud malware family. We have written before about TeamPCP running supply chain attacks like a production pipeline, and this incident fits that characterisation precisely: no CVE, no software defect, no vulnerability to patch. The exploited weakness was a maintainer's account.
Put this incident next to the Mastra npm scope compromise in June and the Nx Console extension compromise, and the shape is consistent. One identity, thousands of downstream consumers, minutes of exposure, and nothing a vulnerability scanner was ever designed to catch.
What actually helps
Pin and verify, do not merely lock. A lockfile records what you resolved; it does not verify what you received. Integrity hashes help only if the version you pinned predates the compromise.
Disable install scripts in CI, deliberately. npm 12 turned install scripts off by default in July 2026 — and this payload still used a preinstall hook, because a great many pipelines pin older npm, override the default, or run through wrappers that re-enable it. We covered how quickly attackers adapted to that change; the keyv payload is the follow-on evidence that the default alone is not the control.
Eliminate long-lived publish tokens. A worm propagates on standing credentials. OIDC trusted publishing gives CI a short-lived, workflow-scoped credential with nothing durable left behind for a payload to steal.
Egress-control your build environment. The second stage had to fetch a Bun runtime from the internet and post credentials outward. A CI network policy with an allowlist breaks both halves.
Know your install timeline. The single hardest question during this incident was "did any of our builds run between the malicious publish and the unpublish." Teams without build-time dependency records could not answer it.
How Safeguard helps
Continuous SBOM at build time, not scan time. Safeguard's Supply Chain Core generates CycloneDX and SPDX inventories on every build, so the record of which exact versions entered which environment at which timestamp already exists when an incident starts. Answering "were we exposed between 14:02 and 16:40 UTC on 4 August" becomes a query, not an archaeology project.
Behavioural delta analysis on version bumps. No CVE existed for any of these versions, and none ever will — the code was not vulnerable, it was malicious. Safeguard evaluates version-over-version behaviour, so a caching library that newly acquires a preinstall hook, newly downloads a runtime binary, or newly reads ~/.aws is flagged on behaviour rather than on a database entry that does not exist yet.
Lion, for the credentials that are the actual loss. Lion enforces just-in-time secret brokering so build environments hold no resident long-lived tokens, capability scoping so a compromised step's reach is bounded, and egress allowlists so a second stage cannot fetch its runtime or exfiltrate what it collected. Against a worm that propagates specifically on stolen publish tokens, removing standing credentials is the control that stops the chain rather than documenting it.
Eagle for honest exposure scoping. After an incident like this the choice is usually between rotating nothing and rotating everything. Eagle establishes which environments actually executed an affected version and which credentials were genuinely reachable from them, which is what makes a proportionate rotation plan defensible.
Griffin for the remediation itself. Pinning safe versions across dozens or hundreds of repositories, opening the PRs, and running the test suites is mechanical work that consumes the first day of every one of these incidents. Griffin authors and tests those changes in parallel.
Go and check whether your CI runners hold a long-lived npm token. That single question separates the organisations that lost a build minute from the ones that lost their registry.
Sources: Aikido · Socket · Chainguard · Wiz · Snyk · SC Media