TL;DR
- Microsoft's ChainDrop analysis (August 4) and JFrog's write-up describe a Mini Shai-Hulud worm that started at
keyv@6.0.0andcacheable@2.5.1, then spread to 400+ packages and 1,700+ versions by August 5. Aikido's count put combined installs above 2 billion/month. - The entry point is a
preinstall": "node setup.mjs"hook that downloads Bun and runs an obfuscated collector (Math_Symbol.js/math_init.js). On CI it stays attached to the job so it can read runner memory and OIDC publish tokens, not just files on disk. - Propagation is the scary part: stolen npm tokens trigger tarball surgery (bump patch, inject hook, republish). Many malicious versions had no matching git commit but still carried valid provenance from the publisher's GitHub Actions identity.
- For
ghp_/gho_tokens withworkflowscope, JFrog traced aRun Copilotworkflow that assigns${{ toJSON(secrets) }}to an env var, uploads it asformat-results.txt, then deletes the branch and run. Hunt that artifact name, not just the preinstall IOC. - Reader action: if a compromised version ran on a laptop or CI runner, revoke npm/GitHub/cloud tokens, rebuild the host, audit every writable branch for
.vscode/tasks.json+.claude/settings.jsonhooks and the branchdependabot/github_actions/format/setup-formatter. On npm 12+, dependencypreinstallhooks are blocked by default unless listed inallowScripts(npm RFC 0054).
How ChainDrop differs from "another compromised package"
The shape rhymes with November 2025's Shai-Hulud wave and the TanStack OIDC compromise we covered in May. Three details in the August primaries are worth separating:
1. Tarball republish without a source commit. Microsoft notes many poisoned patch releases had no pull request, tag, or legitimate release behind them. The attacker modified the registry tarball directly using stolen publish credentials. Your "did main branch change?" check misses this class entirely.
2. Provenance can still look clean. The initial keyv push went through the maintainer's real GitHub account and Actions workflow (Aikido). Downstream worm copies inherited that trust surface. Sigstore attestations tell you which workflow published, not whether the workflow author intended that tarball.
3. GitHub Actions becomes part of the payload. JFrog's workflow trace is specific enough to hunt:
- Create branch
dependabot/github_actions/format/setup-formatter. - Add
.github/workflows/codeql_analysis.yml(commit messageAdd CodeQL Analysis, author forged asgithub-advanced-security[bot]). - Workflow name
Run Copilotwrites secrets toformat-results.txtvia${{ toJSON(secrets) }}, uploads artifactformat-results. - Download artifact, delete workflow run and branch.
That is a different detection problem than blocking setup.mjs on install. Egress filters will not see it; GitHub audit logs and artifact retention policies might.
The install hook and the CI path
Every infected package adds setup.mjs plus a ~700 KB JavaScript payload. The loader prefers a local Bun binary; otherwise it pulls Bun 1.3.13 from GitHub releases (JFrog), which is a normal-looking fetch.
On developer machines the sample detaches and keeps running after npm install finishes. On GitHub Actions it stays in-process so it can:
- Read
ACTIONS_ID_TOKEN_REQUEST_TOKEN/ACTIONS_ID_TOKEN_REQUEST_URLfor OIDC tokens used in npm trusted publishing (Aikido). - On Linux runners, use
sudo python3to readRunner.Workerprocess memory and extract objects markedisSecret:true(JFrog).
Exfiltration goes to a dynamic HTTPS endpoint (domains rotated via Ethereum contract 0xE1f2395ee43e45A1556EC6438a88c31B83493103) or, on failure, a public GitHub repo whose description is Shai-Hulud: Here We Go Again. Treat any match on that string as a compromise receipt, not a meme.
Community-spread generations rename the payload file to math_init.js instead of Math_Symbol.js (Aikido). Hash both names if you are scanning registries or node_modules.
Persistence without another npm install
The worm also commits IDE hooks when it gets repo write access (JFrog):
.vscode/tasks.jsonrunsnode .claude/setup.mjson folder open..claude/settings.jsonrunsnode .vscode/setup.mjson session start.
Commits use message chore: update config and a forged Co-authored-by: claude trailer. Opening the repo in VS Code or Claude Code can re-run the collector even after you scrub node_modules.
What to do if you might have pulled a bad version
Assume compromise if keyv@6.0.0, cacheable@2.5.1, or any package on JFrog's growing compromised list landed in a lockfile during the window.
- Revoke first. npm tokens with
bypass_2faand package write scope are the worm's fuel (JFrog). Rotate GitHub PATs/OAuth/App tokens, cloud keys, Vault tokens, and Kubernetes secrets reachable from the host. - Rebuild CI runners and dev laptops from clean images. Cleaning
node_modulesis not enough if runner memory or IDE hooks already ran. - Audit GitHub, all branches. Search for the five hook files, branch
dependabot/github_actions/format/setup-formatter, workflowcodeql_analysis.ymlwith artifactformat-results, and repos describedShai-Hulud: Here We Go Again. - Tighten install policy. npm 12+ blocks dependency lifecycle scripts unless approved in
package.jsonallowScripts(GitHub changelog). JFrog explicitly notes infectedpreinstallhooks do not run on npm 12 by default. On npm 11,ignore-scripts=truein CI.npmrcis the blunt equivalent (npm config). Pair either with pnpmminimumReleaseAgeso a worm's same-day patch bump does not enter your pipeline immediately. - Review trusted publishing. JFrog documents a targeted OIDC path when
GITHUB_WORKFLOW_REFcontainsrelease-drafter.ymlinopensearch-js, including Sigstore bundles minted from the real workflow context. Inventory which workflows can publish to npm and whether a fork PR could reach them.
Sources
- Microsoft Security Blog, ChainDrop supply chain compromise: Anatomy of a self-propagating worm (2026-08-04) — https://www.microsoft.com/en-us/security/blog/2026/08/04/chaindrop-supply-chain-compromise-anatomy-self-propagating-worm/
- JFrog Security Research, Major Shai Hulud campaign strikes npm again, affecting keyv and 400+ packages (2026-08-04) — https://research.jfrog.com/post/shai-hulud-is-back-august/
- Aikido, Keyv and friends compromised in active Shai-Hulud supply chain attack (2026-08-04, updated 2026-08-05) — https://www.aikido.dev/blog/keyv-and-friends-compromised-in-npm-supply-chain-attack
- GitHub Changelog, npm install-time security and GAT bypass2fa deprecation (2026-07-08) — https://github.blog/changelog/2026-07-08-npm-install-time-security-and-gat-bypass2fa-deprecation/
- npm RFC 0054, Make install scripts opt-in — https://github.com/npm/rfcs/blob/main/accepted/0054-make-scripts-install-opt-in.md
- npm
ignore-scriptsconfig — https://docs.npmjs.com/cli/v10/using-npm/config#ignore-scripts - pnpm
minimumReleaseAge— https://pnpm.io/settings#minimumreleaseage - Datadog Security Labs, Shai-Hulud 2.0 npm worm (November 2025 precedent) — https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/