Kai Ole Hartwig
12 min read
Critical

Keyv/Cacheable npm Compromise: Mini Shai-Hulud Worm Poisons Hundreds of Packages via preinstall Hook and Claude Code/VS Code Autostart

On 4 August 2026, the release pipeline for keyv (619M downloads/month per Snyk) and roughly a dozen related packages from the cacheable ecosystem was compromised. A newly inserted preinstall hook loads an encrypted Bun-runtime payload (Math_Symbol.js, about 728 KB) that harvests cloud (AWS/GCP/Azure), CI/CD, npm, and GitHub credentials. The worm self-propagates by using stolen publish tokens, and thanks to npm's Trusted Publishing attestation it even produces cryptographically “genuine” signed malicious releases. It also drops autostart hooks into .claude/settings.json and .vscode/tasks.json — code then runs the moment a cloned repo is opened in Claude Code or VS Code, without any npm install. Snyk tracks the incident as SNYK-JS-KEYV-18515941 (Critical); no official CVE number had been assigned at the time of this article.

TL;DR — 90 seconds

Affected?

Projects with a direct or transitive dependency on keyv@6.0.0, cacheable@2.5.1, cacheable-request@13.0.20, flat-cache@6.1.24, file-entry-cache@11.1.6, cache-manager@7.2.10, and several scoped @cacheable/* packages (including net, node-cache, memory). flat-cache/file-entry-cache reach many teams indirectly via ESLint.

Risk?

Critical. A preinstall hook runs code with developer/CI privileges on a plain npm install, downloads an encrypted Bun payload, and harvests cloud, CI/CD, and registry credentials. The worm also plants autostart hooks in .claude/settings.json and .vscode/tasks.json that trigger simply by opening a cloned repo — even without installation.

Immediate action?

Pin affected packages to last-known-clean versions via overrides/resolutions, rebuild lockfiles without executing scripts (--ignore-scripts), and check for the documented IOCs — before rotating tokens (the worm watches for token revocation and triggers additional payloads when it happens).

Recommendation?

Every team with a Node.js/npm build or CI pipeline should check its dependency tree against the affected packages immediately — including transitively, even if none of the top-level dependencies look directly affected.

Criticality?

critical — an active, self-propagating supply-chain worm with credential theft, IDE persistence, and an anti-forensics mechanism against token rotation.

What is the problem?

According to converging analysis from Snyk, Socket.dev, Cloudsmith, Wiz, and other vendors, the release pipeline for keyv — maintained by jaredwray — was compromised on 4 August 2026. Per Snyk's reconstruction, malicious commits landed in the keyv repository around 09:02–09:17 UTC, adding a preinstall script hook ("preinstall": "node setup.mjs") along with the associated payload files. At 09:35 UTC, keyv@6.0.0 was published to npm with this hook — including a valid npm provenance attestation, since the build process itself was unmodified and ran legitimately via GitHub Actions. That's the central point: npm provenance attests that a given workflow built an artifact — not that the source code fed into that workflow is trustworthy. If the maintainer account, repo access, or CI credentials were compromised before publication, provenance signs a malicious artifact just as readily as a clean one.

The preinstall hook loads a roughly 29.9 KB loader (setup.mjs) that detects platform and architecture, downloads a standalone Bun runtime, and uses it to run a roughly 728 KB, polymorphically base91-encoded second stage (Math_Symbol.js) — outside the usual Node-based monitoring paths. Between 10:09 and 10:14 UTC the cacheable family followed, then further packages such as ecto@5.0.1 (10:28 UTC). The worm self-propagates: it searches for further packages by the compromised maintainer(s), injects the same preinstall hook, recomputes integrity/shasum fields, bumps the version, and republishes via npm's Trusted Publishing — so follow-on releases also carry valid attestations.

A second trigger path, independent of the install step, makes this incident particularly notable: the compromised repository additionally contains .claude/settings.json with a SessionStart hook and .vscode/tasks.json with a runOn: "folderOpen" task, each invoking its own setup.mjs. If an affected repository is merely opened in Claude Code or VS Code — no npm install, no build — the loader still runs. That means simply cloning and opening a repo is already an infection path in this incident, not just installing it as a dependency.

Who is affected?

PackagePoisoned versionLast clean version
keyv6.0.05.6.0
cacheable2.5.12.5.0 (verify the last version before the incident)
cacheable-request13.0.2013.0.19 (verify the last version before the incident)
flat-cache6.1.246.1.23
file-entry-cache11.1.611.1.5
cache-manager7.2.107.2.9
@cacheable/net, @cacheable/node-cache, @cacheable/memory and other scoped packagesvaries by package, see Snyk advisory SNYK-JS-KEYV-18515941last version before the incident

Particularly relevant for TYPO3/PHP teams: flat-cache and file-entry-cache reach many projects not as a direct dependency but transitively via ESLint in the frontend build (e.g. TYPO3 sitepackages with an npm-based asset build). A plain npm ls without --all will miss such transitive hits. The exact, complete package list is still evolving as the investigation continues — the current version of Snyk advisory SNYK-JS-KEYV-18515941 and the Socket.dev and Wiz analyses are authoritative.

Impact

Per Snyk and Socket.dev, the payload targets credentials broadly: GitHub and npm tokens, cloud credentials (AWS metadata via IMDSv1/v2, GCP service-account keys, Azure client secrets), HashiCorp Vault tokens, Kubernetes service-account tokens, SSH keys, TLS certificates, database connection strings, and GitHub Actions runner memory (including OIDC tokens and repository secrets). Exfiltration happens via newly created public GitHub repositories and DNS tunneling, among other channels.

Particularly concerning is a documented persistence mechanism (“gh-token-monitor”) that watches stolen GitHub tokens: if a token is revoked, the mechanism executes an attacker-supplied handler — turning the usual “contain, then rotate” incident-response order against defenders. Rotating tokens before removing this persistence can potentially trigger an additional, actively fired malicious-code path.

Because the worm self-propagates via stolen publish tokens while still obtaining valid npm provenance attestations, a trust model based purely on signature verification fails here: “cryptographically signed” explicitly does not mean “trustworthy source code” in this case. Affected are not only production environments but also developer laptops and CI runners — anywhere one of the packages was installed or an affected repository was opened.

Mitigation / immediate measures

Operational decision block

Step 1 — pin affected packages to clean versions

 

{
  "overrides": {
    "keyv": "5.6.0",
    "cacheable": "2.5.0",
    "cacheable-request": "13.0.19",
    "flat-cache": "6.1.23",
    "file-entry-cache": "11.1.5",
    "cache-manager": "7.2.9"
  }
}
// package.json ("overrides" for npm; use "resolutions" for Yarn)

 

Step 2 — rebuild the lockfile without executing scripts

 

npm install --package-lock-only --ignore-scripts
rm -rf node_modules
npm cache clean --force
npm ci --ignore-scripts

 

Step 3 — remove persistence before rotating tokens

 

# hunt for the gh-token-monitor persistence mechanism:
ls -la ~/.local/bin/ ~/.config/gh-token-monitor/ \
       ~/.config/systemd/user/ 2>/dev/null
ls -la ~/Library/LaunchAgents/ 2>/dev/null   # macOS

# take a forensic copy, then remove the persistence
# (stop/disable the systemd unit or unload the LaunchAgent),
# only then proceed to step 4

 

Step 4 — rotate credentials

 

# GitHub token: revoke and reissue
# npm token: revoke (npm token revoke) and reissue
# Also rotate cloud keys (AWS/GCP/Azure), Vault tokens, Kubernetes
# service-account tokens, SSH keys, and database connection strings
# if an affected machine/CI runner had the packages installed

Detection / verification

Check the dependency tree (including transitively)

 

npm ls keyv flat-cache file-entry-cache cacheable-request \
  cacheable cache-manager @cacheable/net @cacheable/node-cache \
  @cacheable/memory --all

 

Hunt for known payload files and IOCs

 

find "$HOME" /tmp -name setup.mjs -o -name Math_Symbol.js \
  -o -name gh-token-monitor.sh -o -name gh-token-monitor.service \
  -o -name com.user.gh-token-monitor.plist 2>/dev/null

# check the process chain: node setup.mjs spawning a downloaded
# bun binary; look for temp directories matching "bun-dl-*"
find /tmp -maxdepth 2 -iname "bun-dl-*" 2>/dev/null

 

Find compromised preinstall hooks in the node_modules tree

 

find node_modules -name package.json -print0 | xargs -0 node -e '
  const fs = require("node:fs");
  for (const file of process.argv.slice(1)) {
    try {
      const pkg = JSON.parse(fs.readFileSync(file, "utf8"));
      if (pkg.scripts?.preinstall === "node setup.mjs") {
        console.log(`${pkg.name}@${pkg.version}`);
      }
    } catch {}
  }
'

 

Check IDE/editor autostart hooks

 

# in every cloned repository, check whether these files exist and
# contain suspicious setup.mjs invocations:
git -C /path/to/repo show HEAD:.claude/settings.json 2>/dev/null
git -C /path/to/repo show HEAD:.vscode/tasks.json 2>/dev/null | grep -A3 folderOpen

 

Check CI/CD and cloud audit logs

Operator guidance

Mid-market

Check the dependency tree of every active Node.js/npm project (including frontend build toolchains such as TYPO3 sitepackages with ESLint) today against the affected package list. Rebuild lockfiles without executing scripts, check for the persistence mechanism, and only then rotate tokens.

Enterprise / multi-repo

Run a centralized scan across all repositories and CI pipelines (SCA tooling or the npm ls --all command above per project), including developer laptops where affected repos were cloned/opened — not just CI. Because the worm propagates via legitimate, attested releases, signature verification alone is not enough; version pins/overrides are mandatory until the affected package list is considered final.

Teams using AI coding tools (Claude Code, Cursor, VS Code)

Specifically check every repository cloned or opened in the last few days for .claude/settings.json and .vscode/tasks.json with folderOpen/SessionStart hooks — this infection path fires regardless of whether npm install was ever run.

Decision block

Act today if: one of the affected packages was found in your tree, or a repo was opened/cloned in the last few days. This week if: Node.js/npm toolchains are in use but no scan has run yet. Watch if: pure PHP/TYPO3 backends without an npm-based frontend build — vigilance is still worthwhile here, since the package list is still evolving.

What I did concretely

I treat npm supply-chain worms as a pipeline-discipline question, not a question about a single bad package. For my own and client projects with an npm-based frontend build (including TYPO3 sitepackages), I checked the dependency tree against the package list above, verified --ignore-scripts as the standard for CI installs, and updated the Renovate configuration to explicitly block the affected versions until the advisory situation settles. I also spot-checked cloned repositories for .claude/settings.json or .vscode/tasks.json files with unusual autostart hooks — with a negative result, but that check now belongs in my standard routine whenever opening someone else's repository, not just when installing it.

Frequently asked questions about the Keyv/Cacheable npm worm

How can a signed npm package be malicious?+

npm's Trusted Publishing attestation proves that a given GitHub Actions workflow built the published artifact — not that the source code fed into that workflow is trustworthy. If the maintainer account or repo access was compromised before publication, provenance signs a malicious artifact just as readily as a clean one.

Is npm install --ignore-scripts enough protection?+

Against the preinstall-hook path, yes — --ignore-scripts prevents the loader from running on install. It doesn't help against the second infection path via .claude/settings.json and .vscode/tasks.json: that one fires as soon as the repo is opened in Claude Code or VS Code, regardless of npm install.

Why remove persistence first and only then rotate tokens?+

Because a documented persistence mechanism (“gh-token-monitor”) watches stolen GitHub tokens and, on revocation, executes an attacker-supplied handler. Rotating first can trigger exactly that additional malicious-code path — the usual incident-response order is deliberately turned against defenders here.

Is this the same as the binding.gyp worm from June 2026?+

No, but it belongs to the same family of self-propagating npm supply-chain worms (the “Shai-Hulud” lineage) we've covered here before — for example the TanStack incident CVE-2026-45321 or Miasma. Affected packages, payload, and trigger paths differ each time; the underlying tactic (lifecycle hook, credential harvesting, automatic self-propagation) is similar.

Does this affect me if I only run PHP/TYPO3 without npm?+

Not directly if your project has no npm dependencies. But a great many TYPO3 sitepackages build their frontend assets through npm-based toolchains (Webpack, Vite, ESLint) — and that's exactly where flat-cache/file-entry-cache frequently land transitively via ESLint. Running npm ls --all inside the sitepackage directory will tell you.

Is there a CVE number yet?+

Not at the time of this article. Snyk tracks the incident under its own advisory ID SNYK-JS-KEYV-18515941 (rated Critical, CWE-506 — Embedded Malicious Code). An official CVE assignment may still follow; until then, the Snyk advisory is the authoritative, continuously updated reference.

Conclusion

The Keyv/Cacheable incident joins an already long series of self-propagating npm worms from the “Shai-Hulud” family — two aspects here are new and notable. First, the worm abuses npm's Trusted Publishing attestation to get its own malicious releases cryptographically signed as “genuine,” showing that provenance verification alone does not detect compromised source-code pipelines. Second, it extends the infection path beyond the classic npm install boundary into IDE autostart hooks (Claude Code, VS Code) — simply opening a cloned repo is enough. Anyone running Node.js/npm toolchains should check their dependency tree, rebuild lockfiles without executing scripts, and start treating repository-local editor configuration files as part of the attack surface going forward — not just package.json.

Sources

I check, mitigate, and validate your npm/CI supply chain against worms like the Keyv/Cacheable incident.

Dependency-tree audit against affected packages, lockfile rebuilds without script execution, Renovate/CI hardening against compromised Trusted Publishing releases, and review of repository-local editor autostart hooks.

Platform operations, not paper consulting: I continuously review, patch, and harden your software supply chain.

About the author

[Translate to English:] Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.