
Scanner for the keyv/cacheable supply-chain attack: detects compromised npm packages, verifies payload hashes, and finds persistence implants in repo and host modes.
English | Português
Standalone scanner for the keyv/cacheable supply-chain incident ("Shai-Hulud: Here We Go Again", Aug 4 2026) — 440+ npm packages compromised by a self-propagating worm that steals cloud/CI credentials and plants persistence with a dead-man's switch.
Detects, in minutes and without installing anything:
package-lock.json, npm-shrinkwrap.json, yarn.lock (v1 and Berry), pnpm-lock.yaml and bun.lock — including transitive dependencies, with the full chain (e.g. eslint → file-entry-cache → flat-cache → [email protected]);node_modules (name + SHA-256 hash of the known artifacts);SUSPECT, never confirmed without a hash;.claude/settings.json and .vscode/tasks.json, temporary artifacts (bun-dl-*);keyv/cacheable families was compromised; the attacker published new versions with a "preinstall": "node setup.mjs" hook — code that runs before the package is installed, with the privileges of whoever ran npm install.setup.mjs downloads the Bun runtime from GitHub and runs the payload in it — evasion against tools that only monitor node processes.Math_Symbol.js (~728 KB, obfuscated) steals credentials: AWS instance metadata, AWS/GCP/Azure keys, Vault tokens, Kubernetes service accounts, GitHub Actions secrets, npm tokens, plus a generic regex sweep for private keys and bearer tokens on disk.npm install/npm ci with lifecycle scripts enabled since 2026-08-04 09:35 UTC. With --ignore-scripts, the hook did not run..claude/settings.json (SessionStart) and .vscode/tasks.json (folderOpen) that run the loader when the cloned folder is opened — no npm install, nothing installed. This includes people who cloned the repo to investigate the incident and AI coding agents that opened the directory — one of the first public cases of AI-agent hooks (.claude/) used as a supply-chain vector.The implant installs a "watcher" (gh-token-monitor) kept alive by a LaunchAgent (macOS) or a systemd user service + loginctl enable-linger (Linux). Every 60 seconds it validates the stolen GitHub token against the API. While the token works, nothing happens. When the response turns 4xx — i.e. the moment you revoke the token — it runs, via eval, the contents of ~/.config/gh-token-monitor/handler: an arbitrary command defined remotely by the attacker. Public analysis does not know what it contains — it could be data destruction, re-implant, ransomware, or nothing. The risk is not assessable; that is why the response order is absolute.
Three properties that change the response:
host mode.[email protected] shipped with a passing SLSA attestation. Provenance attests to build integrity, not source: the legitimate workflow compiled already-trojanized code.package.json (the preinstall hook) and two new files added to the package (setup.mjs, Math_Symbol.js).eslint → file-entry-cache → flat-cache → keyv. That is why the scanner shows the chain in every finding.scan.mjs has the following properties — important for anyone responding to a supply-chain incident:
npm install. Audit the whole of scan.mjs in 15 minutes before running it.--update (download a fresh IOC manifest), explicit and optional.docker run --network=none or an isolated machine: just copy scan.mjs + iocs.json.Requirement: Node.js ≥ 18 (any machine with npm already has it). Download the two files — scan.mjs + iocs.json — and that is it: no installation.
Heads-up: if you cloned this whole repository, the
fixtures/folder contains inert IOCs used in the tests (real names and versions, dummy content — no malware). The scanner skips it automatically and warns in the output; findings from it only appear if you scan it on purpose.
There are two run modes that answer different questions, and that is what decides where to run:
repo mode reads lockfiles and node_modules — and lockfiles live in git, so it can be centralized: one person scans every repository in the company.host mode looks for the implant (watcher, LaunchAgent/systemd, IDE hooks), which lives on the machine where the code executed — that is not in git and cannot be centralized.node scan.mjs repo /folder/with/all/the/repos --json=result.json --html=report.html
Answers "which projects are exposed" in minutes, without involving anyone. Accepts multiple paths; walks subdirectories (monorepos and workspaces included).
For each project with a finding, identify who touched it since 2026-08-04 09:35 UTC (git log, CI logs). Those people run, on their machine:
node scan.mjs # current directory + host, in ~30 seconds
In scope: anyone who (a) ran npm install/npm ci in the window; or (b) merely cloned and opened the folder in VS Code or an AI agent — Vector B needs no install.
Since the cost is ~30 seconds and the funnel can leak (a stray clone, a personal project), the safest internal message is: every developer runs node scan.mjs once and sends the --json/--html to AppSec. Sending is manual by design — the scanner has no telemetry (zero egress).