The keyv worm ate 400+ npm packages in 90 minutes — check if you're exposed

· Install Safe ·

5 min read Original article ↗

Two weeks ago, on August 4 at 09:00 UTC, an attacker compromised a single maintainer's GitHub account and turned keyv — a caching library with 150M+ weekly downloads — into a self-replicating credential-stealing worm. Within 90 minutes it had spread to 400+ packages, including cacheable, cache-manager, cacheable-request, and ecto. The worm even signs off in its own fallback C2 marker: "Shai-Hulud: Here We Go Again."

If you ran npm install on the wrong afternoon, the malware went looking for your AWS keys, GitHub tokens, SSH keys, Kubernetes secrets, HashiCorp Vault stores, crypto keystores — and, notably, your AI agent configs (Claude, OpenAI, Cursor, Gemini, Codex). Then it used any npm tokens it found to republish itself into your packages, bypass_2fa: true. That's the "worm" part.

What made this one different

This wasn't a nuisance crypto-miner. Three things stand out:

  1. It self-propagates. Stolen npm tokens → inject payload → bump patch version → republish. One compromise becomes 400.
  2. It targets AI-agent infrastructure. It establishes persistence via Claude Code hooks (.claude/settings.json) and VS Code tasks (.vscode/tasks.json), and steals AI-agent credentials directly. If you've got coding agents installing dependencies unattended, this is aimed at you.
  3. It forged provenance. In the right GitHub Actions context it generated valid SLSA provenance attestations binding malicious source to packages — so "verified provenance" alone wouldn't have saved you.

Are you exposed? Check now (2 minutes)

Fast grep — look for the payload and persistence artifacts:

# The stage-two payload file
find . -path '*/node_modules/keyv/Math_Symbol.js'

# Persistence hooks the worm writes
grep -rl "_NODE_RUNTIME_INIT" . 2>/dev/null
find . -name 'setup.mjs' -path '*node_modules*'

# Known-bad file hashes (SHA-256)
#   math_init.js  9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc
#   setup.mjs     fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb

Network IOCs to block/hunt: npm-cache[.]com, js-mirror[.]com, pypi-get[.]com, awqhnjewqjkl[.]icu, and a C2 dead-drop resolved via an Ethereum contract. Watch for calls to the cloud metadata service (169.254.169.254, 169.254.170.2) from build steps that have no business making them. A suspicious User-Agent: Bun/1.3.13 and /tmp/bun-dl-*/ directories are also tells.

Known-bad versions include keyv@6.0.0 and the @keyv/* redis, sqlite and mongo variants at 6.0.0; cacheable@2.5.1, @cacheable/net@2.1.1, @cacheable/node-cache@3.1.2, @cacheable/memory@2.2.1 and @cacheable/utils@2.5.1; flat-cache@6.1.24, file-entry-cache@11.1.7, cacheable-request@13.0.20, cache-manager@7.2.10, ecto@5.0.1 and @thiennq/docs-viewer@1.6.2 — plus 400+ others. Because the worm republishes anything a stolen npm token can reach, treat this as a starting point, not a complete inventory.

Or just scan it: paste your repo into InstallSafe /scan and it'll flag any compromised keyv-family versions (and every other known-malicious or vulnerable version) in your dependency tree using live OSV.dev advisory data. Free, no signup, nothing installed.

If you're hit

  1. Treat the machine as compromised — rebuild CI runners and dev boxes that installed affected versions.
  2. Remove the persistence hooks first: .claude/settings.json and .vscode/tasks.json, plus ~/.local/bin/gh-token-monitor.sh and ~/.config/gh-token-monitor/. Rotating before you strip persistence just hands the attacker your new secrets.
  3. Then rotate everything: cloud keys, GitHub/npm tokens, SSH keys, and any AI-agent API keys.
  4. Audit for lateral spread — check your npm account for versions you didn't publish, and GitHub for unauthorized repos, branches, or Actions runs.

Harden against the next one

The specific versions matter less than the pattern. Four changes would have blunted this one:

  1. Pin exact versions and commit lockfiles. The ^ and ~ ranges are what silently pulled the poisoned patch bump. If you find an affected version, pin to a release published before August 4 2026, delete node_modules and the lockfile, and regenerate with exact versions and integrity hashes.
  2. Run installs with --ignore-scripts wherever your toolchain allows it. The entire payload here arrived through a preinstall lifecycle script.
  3. Quarantine brand-new versions. A cooldown window before a freshly published version is allowed into CI is exactly the gap this worm exploited.
  4. Stop trusting provenance alone. These tarballs carried valid attestations. Signature checks passed. Policy at the install boundary is the control that actually held.

The uncomfortable lesson: scanning after the fact is losing

Every defense that runs after npm install — the npm vulnerability scanners and SCA tools that catalogue known CVEs, SBOM diffs, post-hoc audits — lost this race. The worm stole your secrets during the install lifecycle script, before any dashboard lit up red. Even provenance checks got forged.

The only defense that structurally works is blocking bad versions at the install boundary, before npm ever fetches them. That's the entire premise of InstallSafe — a drop-in npm registry proxy that filters known-malicious and vulnerable versions using live OSV.dev data, so the compromised tarball never reaches your machine. Two config lines, byte-for-byte tarballs (your lockfile hashes stay valid), works with npm/yarn/pnpm/bun and CI:

npm config set registry https://r.installsafe.io
npm config set //r.installsafe.io/:_authToken <your-token>

It won't catch a zero-hour compromise the instant it's published — nothing purely advisory-based can — but it slams the door on every version the moment it's flagged, across your whole org and every CI runner, with zero workflow change. And if you've got AI agents installing dependencies unattended — the exact target of this worm — a firewall at the registry is the one place you can actually govern what they pull.

Find out if the worm is already in your tree — run the free InstallSafe scan. No signup, nothing installed.

Related reading: how registry-level blocking compares to Socket.dev and Snyk for npm supply-chain security.


Sources: Wiz, Datadog Security Labs. Hardening guidance follows the OpenSSF npm best-practices guide.