npm supply-chain incident · Aug 2026 · Shai-Hulud

One npm install was enough.
Check your tree with one file.

On August 4, 2026, malicious versions of keyv, cacheable, cache-manager, flat-cache and seven sibling packages shipped to npm and started stealing credentials on install. These are two standalone, read-only scanner scripts — one PowerShell, one bash — that sweep your local projects for the published indicators of compromise.

What happened, whether you're affected, how to run the scan, and what to do with a finding. Indicators researched and published by Aikido Security.

2B+
monthly installs of the poisoned packages
11
packages in the first wave
434+
packages after worm spread
6
checks, all read-only
1
file to download & run

01 — The incident

A preinstall hook did all the work

Attackers took over the keyv maintainer's GitHub account and cut releases with valid GitHub Actions signatures — the packages passed every normal supply-chain check. The campaign brands itself “Shai-Hulud: Here We Go Again”.

1 · Infect
Releases ship two extra files (setup.mjs, Math_Symbol.js) plus "preinstall": "node setup.mjs" — the payload runs the moment npm install touches the package.
2 · Harvest
The 728 KB payload grabs npm and GitHub tokens, AWS / cloud credentials, Kubernetes and Vault secrets, Stripe and Slack keys, .env files and SSH keys via ~200 filesystem patterns.
3 · Exfiltrate
Loot is encrypted and pushed to attacker-controlled GitHub repos (1,300+ found) or the fallback domain npm-cache[.]com.
4 · Spread
Stolen npm tokens publish more infected packages — 434+ packages within hours — and persistence lands in .vscode/tasks.json and .claude/settings.json so your editor or AI agent re-runs it later.
Where the indicators come from
Everything this scanner knows — affected packages and exact versions, payload filenames, SHA-256 hashes, exfiltration infrastructure — was researched and published by Aikido Security in their write-up of the attack. The scripts only automate checking your machine for what their research turned up. Read their post for the full story and current remediation guidance.

02 — Am I affected

You depend on these. The question is which version.

These are transitive dependencies of half the npm ecosystem — keyv alone sees ~604M monthly downloads, and the eleven first-wave packages combine to over 2 billion. Malicious only in the exact versions below, published August 4 and since pulled. If you — or your CI, or a coding agent — ran npm install in that window, spend ten minutes and scan.

PackageBad versionPackageBad version
keyv6.0.0cache-manager7.2.10
flat-cache6.1.24@cacheable/node-cache3.1.2
file-entry-cache11.1.6@cacheable/utils2.5.1
cacheable-request13.0.20@cacheable/net2.1.1
cacheable2.5.1ecto5.0.1
@cacheable/memory2.2.1+ payload filenames carried over from the Nov 2025 wave

03 — Run the scan

Download one file. Read it. Run it.

Each script is a single self-contained file — no install, no dependencies, no companion files. It only reads your disk: nothing is modified, deleted, or quarantined. Open it on GitHub first if you like; they're short on purpose.

Windows · PowerShell

irm https://raw.githubusercontent.com/apsolut-public/scripts/main/security/scan-npm-shai-hulud.ps1 -OutFile scan.ps1
.\scan.ps1 -Path D:\sites

Linux / macOS / WSL · bash

curl -fsSLO https://raw.githubusercontent.com/apsolut-public/scripts/main/security/scan-npm-shai-hulud.sh
bash scan-npm-shai-hulud.sh ~/sites
0
exit — nothing found
1
exit — findings, triage them
2
exit — bad usage

Both versions run the same checks, print the same counts, and return the same exit codes — either one drops straight into CI or a pre-commit hook. -Quiet / --quiet prints findings only. -Deep / --deep extends the content sweep into node_modules — off by default because it dominates runtime on vendored trees while other checks already cover packages by filename and version.

04 — The six checks

Filename, hook, version, workflow, persistence, strings

One full directory walk total — about 5–6 min on a 7.5-million-file tree — then every check runs against that list. All read-only.

1 · Payload files
setup.mjs, math_init.js, Math_Symbol.js, plus the Nov 2025 bun payloads — SHA-256 printed with every hit.
2 · Preinstall hooks
Any package.json whose preinstall launches a payload.
3 · Bad versions
The eleven compromised versions installed under node_modules — scoped names included, nested trees resolved to the right package.
4 · Actions workflows
GitHub Actions workflows that dump the entire secrets context (toJSON(secrets)).
5 · IDE persistence
Malicious entries planted in .vscode/tasks.json or .claude/settings.json.
6 · Exfil strings
The fallback domain, the campaign marker, the attacker's wallet address — in text files, skipping binaries.
Known false positives — and why they're reported anyway

regenerate-unicode-properties, a very common transitive dependency, legitimately ships a file called Math_Symbol.js full of unicode data. Those hits are reported as ignored: with a count instead of findings — unless the hash matches a known-bad sample. A real payload planted at that path is still caught.

Scanning a tree that contains the scripts repo itself will flag the scripts and their README under check 6 — they carry the indicator strings by definition. Each script skips its own file, but not its sibling.

05 — If it finds something

Assume the tokens are burned

A hit with a non-matching hash means an unknown variant, not a clean file. Triage every finding by hand — then act in this order, because credential theft is the whole point of the malware.

StepActionWhy first
1Rotate credentials — npm tokens, GitHub PATs/OAuth/Actions tokens, cloud keys, anything that lived in a .env on that machine.Stealing these was the payload's only job; everything else is cleanup.
2Audit your GitHub account — repositories you didn't create, workflow files you didn't write.The campaign exfiltrates through victim-owned repos.
3Reinstall clean — delete node_modules, pin or override to safe versions, reinstall from the lockfile.Removes the payload the scanner found.
4Check persistence.vscode/tasks.json and .claude/settings.json for entries you didn't put there.Otherwise your editor or agent re-infects the clean tree.
5Follow Aikido's remediation in the write-up — they also offer a free repo scan and install-time blocking via their tooling.They found the campaign; their guidance tracks it as it evolves.
The honest limit
This is an incident-response script for the first days of a fresh attack, when the indicators exist only in a blog post and no scanner has ingested them yet. It knows exactly one campaign. For known, catalogued vulnerabilities use the maintained tools — osv-scanner, npm audit, or Aikido. The indicator lists live in one marked block at the top of each script; when new IoCs are published, that block is the only thing that changes.

06 — Questions

Asked and answered

Is it safe to run a random security script from the internet?

Don't take that on faith — that skepticism is exactly what this incident rewards. Each script is one file, short by design, with every indicator in a marked block at the top. Open it, read it, then run it. It only reads your disk and writes nothing but console output and a temp file list.

npm already pulled the bad versions — why scan at all?

Pulling stops new installs. If a poisoned version landed on your machine during the window, the payload already ran: your tokens may be gone and persistence may be planted. The scan answers "did it touch me", which the registry can't.

The scan flagged a file but the hash doesn't match — false positive?

Not automatically. A matching hash confirms the exact published sample; a non-matching hash at a payload path means an unknown variant until proven otherwise. Known-benign collisions (like regenerate-unicode-properties) are already filtered to ignored:. Triage by hand and rotate tokens before writing anything off.

What's the single next step?

Copy the two-line command for your platform from §03 and run it against your projects root. Ten minutes, read-only, and exit code 0 lets you stop thinking about this.

07 — Source

Two files, nothing else

FilePlatformStatus
scan-npm-shai-hulud.ps1Windows · PowerShell 7+maintained
scan-npm-shai-hulud.shLinux / macOS / WSL · bashmaintained

Part of apsolut-public/scripts — standalone developer scripts: every one a single file you can download or copy-paste and run, no install, no shared library.