Alle Artikel

The keyv and cacheable npm Supply Chain Attack: Inside the Mini Shai-Hulud Campaign

Quincy Castro, CISO

TL;DR

  • What happened: On August 4, 2026, an attacker took over the GitHub account of Jared Wray (jaredwray), the maintainer behind keyv and cacheable, and published malicious versions of ten widely used npm caching packages.

  • Affected packages: keyv, cacheable-request, cache-manager, @cacheable/utils, flat-cache, file-entry-cache, cacheable, @cacheable/memory, and @cacheable/node-cache — the nine packages maintained directly by the compromised account, plus hundreds of additional downstream packages swept up through worm-like propagation. Counts are still climbing, with trackers reporting anywhere from roughly 400 to over 2,200 affected packages and artifacts.

  • Scale: ten core packages representing over two billion combined monthly downloads across the affected ecosystem.

  • Risk: a credential-stealing dropper that harvests npm, GitHub, AWS, and HashiCorp Vault secrets. It exfiltrates them to the domain npm-cache[.]com and to GitHub repositories set up under compromised identities, and it looks up its command-and-control domain from an Ethereum smart contract so the operator can change infrastructure without touching the payload.

  • Chainguard customers: Not affected. Chainguard uses multiple layers of defense to ensure malicious packages are unavailable for consumption. Malware and greyware scanning, along with configurable cooldowns, kept these packages from reaching customers.

  • What to do: check your lockfiles for the versions listed below and rotate any exposed credentials immediately.

At around 9:00 UTC on August 4, 2026, an attacker used a compromised GitHub account (jaredwray) to push malicious commits directly to the main branch of the keyv repository, then cut a new release. Within minutes, the same payload appeared across eight more packages maintained by the same account. The investigation is ongoing, and the malware has since turned up in hundreds of unrelated packages as well.

Affected packages

Five core packages have confirmed malicious versions: keyv, cache-manager, cacheable-request, @cacheable/utils, and flat-cache. Four more from the same maintainer are confirmed affected, but no bad version number has been published for them yet: cacheable, file-entry-cache, @cacheable/memory, and @cacheable/node-cache. Beyond those nine, the worm's reach is real but hard to pin down. Reported figures range from roughly 400 to over 2,200 packages, and no source has published a complete, independently verified list. Don't trust the latest tag; check the exact version installed against your lockfile.

Confirmed malicious versions:

  • keyv@6.0.0

  • cacheable-request@13.0.20

  • cache-manager@7.2.10

  • @cacheable/utils@2.5.1

  • flat-cache@6.1.24

Confirmed affected, exact malicious versions have not yet been published, so treat any version published on or after  August 4, 2026, as suspect:

  • file-entry-cache

  • cacheable

  • @cacheable/memory

  • @cacheable/node-cache

npm restored keyv@5.6.0, flat-cache@6.1.23, cache-manager@7.2.9, and cacheable-request@13.0.19 as latest, confirming these versions are clean. No confirmed clean version has been published yet for file-entry-cache, cacheable, @cacheable/utils, @cacheable/memory, or @cacheable/node-cache. The @keyv/* adapter packages and the Keyv 5.x line were never affected.

One caveat changes what "clean version" means here. The GitHub source repository remained poisoned even after these versions were restored to latest. The payload files were staged across all 19 packages in the keyv monorepo, so a release cut from that tree, even one published by the legitimate maintainer account, could still carry the payload until the source itself is cleaned. Pinning to a known clean version is only a point-in-time fix, not a permanent one.

These packages are also confirmed affected, sharing the same payload and file hashes: @ornikar namespace (28 packages), @qlik/embed-react, @qlik/embed-runtime, @qlik/embed-web-components, @qlik/runtime-module-loader, @nebula.js/nucleus, @hubsync/web-sdk-react, and several others.

What the malware does

Each affected package received two new files, setup.mjs and Math_Symbol.js, along with a preinstall entry in package.json. Running npm install against an affected version triggers setup.mjs automatically, before a developer ever imports the package.

  • Credential harvesting: the payload reads npm tokens from .npmrc, GitHub tokens (personal access tokens, session tokens, and OpenID Connect (OIDC) tokens), cloud credentials including AWS keys, Kubernetes service account tokens, database connection strings, and other private-key material, and HashiCorp Vault tokens from the VAULT_TOKEN environment variable, falling back to an HTTP request if that variable is not set.

  • Credential validation: before exfiltrating anything, the malware validates stolen npm tokens against npm's registry.npmjs.org/-/whoami endpoint in real time, filtering out tokens that are already dead.

  • Self-propagation: the payload carries its own logic for modifying, version-bumping, and republishing packages under any npm identity it successfully validates, which is what turns a single compromised maintainer account into a spreading worm rather than a contained incident.

  • Exfiltration through blockchain infrastructure: stolen data is sent to a dedicated domain and to GitHub repositories created under compromised identities. The malware separately queries an Ethereum smart contract through public RPC endpoints to dynamically resolve its current command-and-control domain, letting the operator rotate infrastructure without changing the payload. That kind of RPC traffic is common in developer environments and easy to miss at the network layer.

  • Runtime evasion: rather than running under Node.js, the payload downloads the Bun JavaScript runtime from Bun's official GitHub releases and executes through it instead. Because the download comes from a legitimate GitHub URL rather than attacker infrastructure, there's no suspicious domain for egress monitoring or reputation checks to flag.

  • IDE-triggered execution: .claude/settings.json contains a SessionStart hook that calls .vscode/setup.mjs, and .vscode/tasks.json contains an Environment Setup task with runOn: folderOpen that calls .claude/setup.mjs. The two files cross-reference each other. Opening a cloned copy of the keyv repository in VS Code or Claude Code can trigger this chain, with no npm install required. Neither editor runs it unconditionally, though: VS Code blocks automatic tasks in an untrusted workspace by default, and Claude Code applies the same workspace-trust check to repo-supplied settings.

Indicators of compromise

If you installed any affected version (see Affected packages above), treat your environment as compromised.

Network indicators:

  • npm-cache.com (104.21.35.216)

  • eth-mainnet.nodereal.io (35.175.164.77)

  • go.getblock.io (185.44.207.215)

  • eth.llamarpc.com (172.67.167.200)

  • pypi-get.com

  • js-mirror.com

File hashes: 

  • Math_Symbol.js / math_init.js: 35a672cf34b996b91f3e1c28cbf3a05a37e036e4

  • setup.mjs (in .claude): 686aa40d0fc22c8d569494543a0f891f359f2f99

  • setup.mjs (in .vscode): f525d52ceb966516686b482d3dc0137028cc6a63

Hash values are still being cross-verified across research teams. Some published hashes use different algorithms for what appear to be the same files, so treat file names and paths as the more reliable indicator until this settles.

Package-level indicators:

  • Injected manifest entrypackage.json gains "preinstall": "node setup.mjs"

  • Dropper filenode_modules/<package>/setup.mjs, present in every affected package

  • Payload filenode_modules/keyv/Math_Symbol.js and .claude/math_init.js in a compromised repo checkout (same payload, identical hash, different names depending on the trigger path)

  • IDE hook files.claude/settings.json, .vscode/tasks.json committed in the compromised repo

Other artifacts:

  • Bun runtime staging directory/tmp/bun-dl-*/

  • String constantIfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients

How to check if you're affected

A lockfile match alone warrants investigation, even if the package appears unused at runtime.

  1. List installed versions of the affected packages

npm ls keyv cacheable-request cache-manager @cacheable/utils flat-cache file-entry-cache cacheable @cacheable/memory @cacheable/node-cache 2>/dev/null

2. Check your lockfiles for affected packages and versions

grep -E "keyv|cacheable-request|cache-manager|@cacheable/utils|flat-cache|file-entry-cache|cacheable|@cacheable/memory|@cacheable/node-cache" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null

3. Search node_modules for the malicious file artifacts

find . -path "*/node_modules/*" \( -name "Math_Symbol.js" -o -name "setup.mjs" \) 2>/dev/nul

4. Search package.json files for the injected preinstall hook

grep -R '"preinstall": "node setup.mjs"' --include=package.json . 2>/dev/null

5. Check any cloned repo checkouts for the IDE-trigger hook files, separately from your dependency tree

find . \( -path "*/.claude/settings.json" -o -path "*/.vscode/tasks.json" -o -path "*/.claude/math_init.js" \) 2>/dev/null

Immediate steps if you're affected

  1. Don't open a clone of an affected repository in VS Code or Claude Code until you've inspected .claude/settings.json and .vscode/tasks.json for injected hooks — opening the workspace can trigger the payload independent of any npm install.

  2. Do not rotate credentials before checking for persistence artifacts. No destructive trigger has been confirmed for this variant, but prior versions of this malware lineage have shipped exactly this kind of token-revocation-triggered logic. Check for persistence artifacts first.

  3. Downgrade to a known-clean version. npm restored keyv@5.6.0, flat-cache@6.1.23, cache-manager@7.2.9, and cacheable-request@13.0.19 as latest, and these are confirmed clean. No confirmed clean version exists yet for file-entry-cache, cacheable, @cacheable/utils, @cacheable/memory, or @cacheable/node-cache, so pin those to a version published before August 4, 2026, that you've verified independently. The source repository remained poisoned even after clean versions were restored, so treat any pinned version as a point-in-time fix, not a permanent one. Confirm with npm ls after downgrading.

  4. Rotate exposed credentials, in order: npm publish tokens, GitHub personal access tokens and OIDC tokens, Amazon Web Services (AWS) access keys and session tokens, Kubernetes service account tokens, database credentials, and HashiCorp Vault tokens.

  5. Block egress to npm-cache.com, pypi-get.com, and js-mirror.com, and treat unexpected outbound traffic to Ethereum RPC endpoints from build and CI/CD environments as suspicious.

  6. Audit cloud and source control logs for unauthorized access following package installation, particularly any npm publishes you did not initiate.

  7. Rebuild affected systems rather than relying on package removal alone. Any computer that ran the preinstall hook should be treated as potentially compromised.

  8. Upgrade to npm 12 or newer, or install with --ignore-scripts. npm 12 blocks unapproved dependency lifecycle scripts by default. Earlier npm clients and other install paths that still allow lifecycle scripts remain exposed to this kind of attack.

Why Chainguard customers were protected

Chainguard Libraries customers were never exposed to this campaign. Not a single customer pulled an affected version of these packages.

Chainguard Libraries applies multiple layers of protection. For the newest and most popular packages, Chainguard builds from verified source. Across the catalog more broadly, malware and greyware scanning and cooldown periods run before any new release becomes available to customers. In this case, scanning and cooldowns were the layers that gave Chainguard the opportunity to catch this campaign before it could reach customers, regardless of how the malicious code entered the package in the first place.

That's the same protection model that's kept Chainguard customers out of every major supply chain incident this year. While the rest of the industry spends the day on triage and credential rotation, Chainguard customers keep shipping.

The bigger picture

This is the third documented wave of the same Shai-Hulud lineage toolkit in 2026. Semgrep traced the identical hook files, the same setup.mjs filename, and the same Bun 1.3.13 download to an April 2026 compromise of the PyTorch Lightning PyPI package. The same lineage hit the @antv ecosystem in May, tied to the TeamPCP group. keyv and cacheable are the latest, in August. A few takeaways stand out.

  1. This isn't a new pattern showing up again. It's the identical toolkit, reused across ecosystems, from PyPI to npm, over a period of months.

  2. Attackers are diversifying their command-and-control infrastructure to blend in with normal developer traffic. This wave's use of Ethereum RPC endpoints follows the same logic as prior waves' use of C2 infrastructure disguised as OpenTelemetry trace ingestion.

  3. Provenance and attestation prove who published a package, not that the publisher's environment was trustworthy. A compromised maintainer account still produces a validly signed, still-malicious release.

  4. No named actor has claimed this campaign. The initial access vector is still unknown, and neither the maintainer, npm, nor GitHub has issued a public statement as of this writing.

Get in touch with our team to learn how Chainguard can protect your software supply chain.

Sources

Want to learn more about Chainguard?

Attacks like this one, a single compromised maintainer account turning into a self-propagating worm across hundreds of packages, are becoming a regular occurrence in the open source ecosystem. Chainguard Libraries lets your team turn off live access to npm, PyPI, and Maven Central and replace it with secure-by-default endpoints instead. Our dependency catalog applies multiple layers of security controls, including building the newest and most popular packages from verified source, malware and greyware scanning, and cooldowns, so a package only becomes available to your team after it's cleared all of them.

Learn more about Chainguard Libraries and how it protects your software supply chain.

Share this article

Verwandte Artikel

Want to learn more about Chainguard?

Contact us