Services Case Studies Insights About Start a project →

The keyv npm supply chain attack: what happened, and how to respond.

Security Published August 17, 2026 7 min read

Why a worm-scale npm attack changes the calculus

On August 4, 2026, an attacker took over the GitHub account of Jared Wray, the maintainer behind keyv, cacheable, and eight other widely used npm caching packages, and pushed malicious code straight to the main branch of each one. Within minutes, new versions were live on npm, cryptographically signed with valid GitHub Actions provenance, built and published through the exact same automated pipeline every legitimate release had gone through for years. The nine core packages Wray maintained directly, keyv, cacheable-request, cache-manager, cacheable, flat-cache, file-entry-cache, @cacheable/utils, @cacheable/memory, and @cacheable/node-cache, represent more than two billion combined monthly downloads. Worm-like propagation logic in the malicious payload then pushed compromised versions into hundreds of downstream packages that depend on them, expanding the blast radius well beyond anything a single maintainer's account should be able to reach. By the time researchers had it contained, 2,236 package versions across the family had been compromised.

What makes this attack worth a deep look, rather than a footnote in a weekly security digest, is not the scale on its own. It is that every control most teams already had in place, lockfiles, provenance verification, a trusted and long-established maintainer, failed to stop it, because the attack did not exploit a gap in those controls. It exploited the fact that a real human with real, unrevoked publishing rights had their account taken over, and everything downstream of that account was, by design, supposed to be trusted.

What actually happened, mechanically

Once the attacker had control of the maintainer's GitHub account, the attack itself was straightforward. Malicious commits went directly to the main branch of each affected repository, and a new release was cut immediately afterward, before anyone reviewing the account activity would have had a realistic window to notice and revoke access. Every affected package version received two new files, setup.mjs and Math_Symbol.js, along with a "preinstall" entry added to package.json pointing at setup.mjs. That single line meant the malicious script executed automatically, before the rest of the install even completed, on every machine that ran npm install against an affected version, with no developer action beyond a routine dependency install required.

The payload itself was a credential-stealing dropper. On execution, setup.mjs downloaded the Bun runtime and used it to run a heavily obfuscated, roughly 728 KB stealer that scanned the host machine for .npmrc publish tokens, GitHub CLI tokens, AWS credentials, HashiCorp Vault tokens, Kubernetes configuration files, and cryptocurrency wallet data, then exfiltrated whatever it found. The design targets exactly the credentials that live on developer laptops and, more consequentially, on CI runners, where a single compromised build job can hold publish rights, cloud infrastructure access, and secrets management tokens all at once.

Why signed provenance did not stop it

Teams that adopted npm's provenance attestations, the cryptographic proof, generated via GitHub Actions OIDC, that a published package was built from a specific commit in a specific repository through a specific workflow run, had good reason to think they had closed the biggest supply chain hole. Provenance genuinely does solve a real problem: it makes it much harder for an attacker with a stolen npm publish token to quietly push a malicious release from their own laptop, because the published package would carry no matching attestation.

That is not the attack that happened here. The attacker did not need a stolen npm token, because they controlled the GitHub account with commit access to the source repository itself. Every malicious release went through the real CI pipeline, built from the real, now-poisoned, source, and signed by the real workflow. Every attestation was completely valid, because provenance proves the package matches what the pipeline built, not that what the pipeline built is safe. Any team using "has valid provenance" as a sole supply chain gate would have watched all 2,236 malicious versions sail straight through that check with a green light attached.

What provenance actually verifies, and what it does not

  • It verifies build integrity. The published artifact matches the source and workflow that produced it, ruling out a class of attacks involving stolen registry tokens and off-pipeline publishes.
  • It does not verify source integrity. Nothing about provenance checks whether the commit that triggered the build was itself authorized, reviewed, or pushed by someone who should have had access.
  • It does not verify intent. A maintainer account with real, unrevoked write access can push anything, and provenance will faithfully attest to it.

The specific gaps most pipelines still have

Most engineering teams we talk to have some subset of the relevant controls in place already. Very few have all of them, and this attack found the gaps.

  • Lifecycle scripts running by default. A standard npm install executes preinstall, install, and postinstall scripts automatically unless a team has explicitly disabled that behavior. This is the actual delivery mechanism in the keyv attack, and it works identically whether the malicious code arrives via a compromised maintainer account, a stolen publish token, or a typosquatted package name.
  • Floating version ranges in package.json. Caret and tilde ranges mean a routine npm install on any given morning can silently pull a version published an hour earlier, with no code change on your side to review. A committed lockfile only protects a build that actually runs npm ci rather than npm install, and plenty of CI configurations still get this wrong.
  • Reputation treated as a risk signal instead of an attack surface. keyv had years of history and billions of downloads, more of both than most internal tooling. That track record is exactly why it was a high-value target, not a reason to trust it more than a newer, less entrenched package.
  • Long-lived, overscoped CI credentials. The stealer specifically targeted AWS keys, Vault tokens, and Kubernetes configs, because those are the credentials most likely to sit in a CI runner's environment with far more reach than the specific job actually needs.
  • No visibility into install-time network activity. A dependency silently downloading an entire secondary runtime during installation and making outbound calls to infrastructure unrelated to the npm registry should be a loud, alertable signal. In most pipelines today, it produces no signal at all.

A practical response for engineering teams

None of the fixes here require exotic tooling, but they do require someone to actually go do the work before the next worm, not after.

  • Disable lifecycle scripts in CI by default. Run installs with --ignore-scripts as the default posture, and re-enable script execution only for the specific packages your team has reviewed and genuinely needs it for.
  • Pin exact versions rather than ranges. Treat a caret or tilde range in package.json as an explicit, reviewed risk decision rather than the default state, particularly for packages with deep transitive reach like caching and logging utilities.
  • Layer dependency scanning on top of provenance, not instead of it. Provenance and npm audit signatures are necessary but not sufficient. Pair them with SBOM-based scanning that flags new maintainer accounts, unusual publish timing, and lifecycle script additions between versions, exactly the pattern this attack would have tripped immediately.
  • Scope and rotate CI credentials aggressively. No build job should hold long-lived cloud or secrets-management credentials. Use short-lived, narrowly scoped tokens issued per job, and treat every credential available to a CI runner as one compromised dependency away from exfiltration.
  • Monitor install-time egress. Alert on outbound network calls during dependency installation that are not going to the package registry itself. A legitimate caching utility has no reason to download an unrelated JavaScript runtime mid-install.

The organizations that came through this cleanly were not the ones with the most security tooling installed, they were the ones that had already made --ignore-scripts the default and treated CI credentials as narrowly scoped, short-lived, and disposable well before August 4. Auditing a dependency pipeline for exactly this kind of blast radius, lifecycle scripts, credential scope, lockfile discipline, and egress visibility, is core to the production-hardening work we do as part of our software and web development engagements. If your team cannot currently answer what an average dependency's install script is allowed to touch in your CI environment, that is the place to start.

Keep reading

Hardening your dependency pipeline against the next attack?

Start a conversation →
KT Solutions Assistant

Before we start, please share a few details so we can follow up with you.

Please enter your name and a valid email address.

End this conversation? Your chat will be emailed to us.