Tuesday afternoon, and the npm registry was changing faster than I could refresh it. That was the tell. On August 4, 2026, a worm swept through the npm registry and republished entire package namespaces at roughly one package per second. What started as one suspicious release, [email protected], spread to hundreds of packages across multiple unrelated organizations before the afternoon was over. If you work with Node.js at all, you need to know whether it touched you.

Here’s the honest part up front: this one is sneaky in a way that makes most of our usual habits fail. The poisoned release looks legitimate — it ships with valid SLSA provenance and a GitHub-verified commit. npm audit won’t flag it. Even “just upgrade to the latest version” can leave you exposed, because latest still resolves to a malicious version on many affected package names. The good news is that checking is straightforward once you know what to look for. I ran every command below on a clean test project so you can copy them as-is.

Shipping container stacks at the Port of Rotterdam, a visual metaphor for the npm supply chain
Image: AgainErick via Wikimedia Commons (CC BY-SA 4.0)

What Actually Happened

The first confirmed malicious release was [email protected], published at 09:35 UTC on August 4 — that’s 5:35 PM Manila time. Keyv is a key-value storage abstraction with adapters for Redis, SQLite, Postgres, MongoDB, and more. It’s the kind of package that sits deep inside dependency trees, which is why its reach is far bigger than its own download count.

The attacker added a single line to the package manifest — "preinstall": "node setup.mjs" — and two new files to the publish list: setup.mjs and Math_Symbol.js. Everything else was untouched. Socket’s analysis confirmed the library code was byte-identical to the clean release candidate. The only difference was the hook that runs before your code, on a bare npm install.

The loader downloads a standalone Bun runtime (version 1.3.13, from Bun’s official GitHub releases) and hands off to a roughly 727 KB compiled bundle. That bundle harvests GitHub tokens, npm credentials, AWS, GCP, Azure, Stripe, Vault, Kubernetes, database connection strings, and private keys. It also reads GitHub Actions secrets straight out of runner memory, and it carries the machinery to republish more poisoned packages using the stolen npm identity.

The scale is genuinely alarming. SafeDep’s registry-backed count grew to 2,234 poisoned versions across 444 package names and twelve organizations in about three and a half hours (09:35–13:18 UTC). Aikido’s count is higher by package name but lower by version, and the researchers explicitly say the two figures should not be combined. What matters more than the exact tally: keyv, flat-cache, file-entry-cache, and cacheable-request together see about 1.87 billion downloads a month, and flat-cache plus file-entry-cache ship inside ESLint. That chain — ESLint to file-entry-cache to flat-cache to keyv — means most affected projects never installed any of these by name.

The Two Traps That Will Make Things Worse

Trap one: rotating credentials first triggers the dead-man’s switch

This is the part that scares me. The payload installs a watcher that polls GitHub with the stolen token, and it executes an attacker-supplied handler the moment that token gets revoked. Rotating credentials — the first thing every incident response checklist tells you to do — is the trigger. SafeDep’s guidance is blunt: hunt the watcher down and remove it before you rotate anything.

Trap two: “just upgrade” doesn’t fix it

The poisoned keyv and cacheable releases were unpublished, and the maintainers restored clean versions as latest. I verified this live: [email protected] now returns 404 from the registry, and latest for keyv is back to 5.6.0, flat-cache to 6.1.23, cacheable-request to 13.0.19. But a lockfile that pinned an affected version during the window is still exposed — unpublishing removes the package, not the copy already in your node_modules. And on most other affected names, latest still resolves to a poisoned version. Upgrading alone can preserve the exposure.

Step 1: Check Your Lockfiles

Start with the packages that matter. From your project root:

npm ls keyv flat-cache file-entry-cache cacheable-request cache-manager

If any of them appear in the output, note the exact resolved version. Then grep your lockfile directly:

grep -n -E '"node_modules/(keyv|flat-cache|file-entry-cache|cacheable-request|cacheable|@keyv/|cache-manager)"' package-lock.json

On my clean test project, the keyv entry looks like this — version 5.6.0, no scripts field at all:

"node_modules/keyv": {
  "version": "5.6.0",
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
  ...
}

A poisoned entry would contain "preinstall": "node setup.mjs" in its scripts section — that’s the single signature of this campaign, confirmed by SafeDep and Socket across every affected package. The registry moved too fast for a reliable fixed blocklist, so compare your resolved versions against the affected-package lists in the research posts, not a cached list of current tags. If you’re not sure whether a version is affected, npm view <package>@<version> scripts will show you the manifest’s script hooks directly — though for the keyv family specifically, 6.0.0 is now gone from the registry, which is itself a signal.

Step 2: Scan for the Implants

Check for the payload files and the token watcher’s logs. These commands come back empty on a clean machine — on an affected one, they’ll find the problem:

find . -name "setup.mjs" -o -name "Math_Symbol.js" -o -name "math_init.js"
ls -la /tmp/gh-token-monitor.*

The watcher writes /tmp/gh-token-monitor.{out,err}.log (on Linux), and the payload can also linger as a bun-dl-* temp directory. Remove the installer and implant first. Only after the watcher is gone do you rotate.

Step 3: Check for IDE Hooks in Cloned Repositories

Here’s the twist that makes this worm different. The attacker also committed two config files into the keyv repository itself: .claude/settings.json and .vscode/tasks.json. They’re wired so that opening the project in VS Code or starting a Claude Code session runs the payload — no install required. The commit carries a green GitHub-verified badge with the author spoofed as github-actions[bot], which makes it look like routine bot maintenance. Both files were still on main as of August 4.

So scan any repository you’ve cloned recently:

find . -name "settings.json" -path "*/.claude/*" -o -name "tasks.json" -path "*/.vscode/*"

Then check those files for "SessionStart" hooks or "runOn": "folderOpen" tasks that invoke node scripts. This is the same class of agent-trust problem behind the AI agent security wake-up call I wrote about — and remember, 546 public repositories appeared on August 4 with the description “Shai-Hulud: Here We Go Again” plus a results/ directory. Those are potential exfiltration drop points, not confirmed victims, but if you cloned one to investigate, treat that clone as re-execution. SafeDep’s advice applies to you, too: don’t open suspicious repos you pulled down to read.

Step 4: Assume Exposure and Respond in the Right Order

If any check comes back positive, stop. Do not panic-rotate. The order matters:

  1. Pause CI/CD pipelines that ran installs during the window.
  2. Remove the revocation watcher and the implants (setup.mjs, Math_Symbol.js, bun-dl-* temp dirs, IDE hook files).
  3. Sweep every repository the stolen credentials could reach for the same markers.
  4. Now rotate and revoke everything: npm tokens, GitHub PATs and GITHUB_TOKEN, cloud keys, Vault tokens, Kubernetes service accounts, CI secrets. Revoke rather than just rotate where you can.
  5. Audit your npm accounts for packages published today that you didn’t push, and audit GitHub for unexpected repositories and commits.

Socket’s guidance is to treat any workstation or CI runner that executed an affected version as credential-exposed, full stop. That’s harsh, but it’s the right baseline. The worm republished packages with valid SLSA provenance — genuine provenance, because the real GitHub Actions pipeline built the poisoned source. Trust neither the verified badge nor the provenance here; both prove the pipeline ran, not that the source was safe.

Prevention: Block the Entire Class of Attack

The single most effective control is to stop lifecycle scripts from running silently. I tested this one in WSL:

npm install --ignore-scripts
npm config set ignore-scripts true

That blocks the preinstall hook class entirely — the payload never executes even if a poisoned package lands in your tree. For pnpm it’s enable-pre-post-scripts=false. Newer npm versions go further: npm 12 blocks unapproved lifecycle scripts by default and makes you approve them explicitly — I covered the migration in my npm 12 install-scripts guide. Note that npm approve-scripts only exists on npm 12+; on npm 10 (what my WSL box runs) it’s an unknown command, so check your version first.

Also be realistic about npm audit: it relies on published advisories, and this worm moved too fast for one at reporting time. On my clean test project it returned “found 0 vulnerabilities” — which is exactly what it would tell you on an affected project whose versions aren’t in the advisory database yet. Audit gives you false comfort here. For real behavioral scanning, the researchers recommend tooling like Socket’s scanner and SafeDep’s vet or PMG, which evaluate install hooks as policy — not just known-CVE databases. And if you want the full detection workflow with lockfile forensics and CI/CD checks, my guide to detecting compromised npm packages from last week walks through the broader playbook.

There’s a deeper pattern in all of this. The same Shai-Hulud malware family was linked to an April compromise of the lightning PyPI package, and to Claude publishing malware to PyPI — supply-chain attacks keep coming at the developer environment from every direction. The worm’s IDE hooks are aimed at developers who never installed anything, betting that curious people would clone the repo to read the source.

So where does that leave us? Check your lockfiles, scan for the markers, and treat any machine that executed an affected version as compromised until proven otherwise. That last habit — assume breach, verify clean — is the zero-trust mindset Google argues we need. A worm that ships with genuine signatures and a verified commit is a reminder that the trust model has to shift from “did this come from the right place” to “does this do what it claims.” Your lockfile check this afternoon is the cheapest insurance you’ll buy this month.

Filed under Tech & Gadgets
Last Update: August 5, 2026 by Felix AlterEgo
0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Newest
Oldest Most Voted