ChainDrop: the npm worm that steals your AI assistant's credentials
ChainDrop poisoned 400+ npm packages with 2B monthly downloads in under four hours on 4 August 2026 — and it's the first worm to harvest AI-assistant tokens and hide in Claude Code hooks.
On 4 August 2026, between 09:35 and roughly 13:20 UTC, a self-propagating npm worm now tracked as ChainDrop poisoned more than 400 packages across dozens of unrelated maintainers. StepSecurity counted 444 packages and 2,212 malicious versions in under four hours; the combined install base of the affected packages is on the order of two billion downloads a month. Patient zero was [email protected] — a caching library pulling 153.7 million weekly downloads — published at exactly 09:35:00Z through a legitimate, OIDC-authenticated trusted-publishing workflow.
Two things make ChainDrop worth a write-up rather than another "rotate your npm token" advisory. First, the malicious versions carried valid SLSA provenance and Sigstore attestations — they were cryptographically indistinguishable from authorized builds. Second, ChainDrop is the first worm we've seen that treats your AI coding assistant as both a credential store and a reinfection vector. It reads .claude, .cursor, .openai, and .anthropic auth files, and it plants persistence inside Claude Code hooks and VS Code tasks.json. If your mental model of supply-chain defense stops at "check the signature," this incident is the counterexample.
How the ChainDrop npm worm actually propagates
The mechanics are a refinement of the Shai-Hulud lineage — the same family behind IronWorm in June and the SLSA-attested packages in May. The loop is brutally simple:
- A package ships a
"preinstall": "node setup.mjs"lifecycle hook.setup.mjsdownloads the legitimate Bun v1.3.13 runtime from the realoven-sh/bunGitHub releases, stages it in$TMPDIR/bun-dl-*, and runs the second stage — a 727,680-byte Bun-bundled payload (Math_Symbol.js/math_init.js). - The payload harvests every credential it can reach, then checks each stolen npm token for package-write permission and 2FA-bypass capability.
- For every package that token can publish, it downloads the latest tarball, injects the loader, bumps the patch version, and republishes.
Because the republish path rides GitHub Actions trusted-publishing, the new malicious versions inherit valid provenance. Per Semgrep's analysis, the worm jumped between organizations every two to seven minutes, hitting scopes like @servicetitan, @qlik, @nebula.js, and @ornikar. The Hacker News reported 546 public GitHub repositories created that day carrying the description "Shai-Hulud: Here We Go Again" — the worm's exfiltration dead-drops.
"Provenance proves build origin, not authorization." — Microsoft Security's ChainDrop teardown, 4 August 2026
That sentence is the whole story. The attacker did not forge a signature. They compromised a maintainer's GitHub account, pushed a poisoned commit, and let the project's own release workflow sign the malware for them. Every attestation was real.
The new part: AI-assistant credentials and Claude Code hooks
Previous Shai-Hulud waves grabbed the usual set — SSH keys, ~/.aws, npm tokens, Vault, Kubernetes, GitHub Actions runner secrets scraped straight out of /proc/<pid>/mem. ChainDrop adds a category nobody was watching:
- AI-tool credential files. The payload reads
.claude/credentials.json,.cursor/credentials.json,.openai/auth.json, and.anthropic/auth.json— the API keys your developers pasted into their coding assistants, which are frequently org-billed keys with generous rate limits and, increasingly, tool-execution scopes. - AI-assistant persistence. Using stolen GitHub credentials, the worm commits two files into repositories it can reach:
.claude/settings.jsonwith aSessionStarthook that re-launches the dropper the next time a developer opens Claude Code in that repo..vscode/tasks.jsonwith a task set torunOn: folderOpen, executingsetup.mjsthe moment the folder is opened in VS Code.
Both commits were disguised as authored by claude <[email protected]> or github-actions[bot] with a verified signature. The effect is a developer-to-developer infection path that does not require anyone to run npm install. A teammate clones the repo, opens it in their editor, and the workspace-trust prompt they've long since learned to click through re-detonates the payload. This is T1547.010-style startup persistence grafted onto the tools developers now trust the most.
The lesson is uncomfortable: as AI coding assistants become privileged actors in the SDLC — reading your code, holding API keys, executing shell commands through hooks — their config files become the new ~/.bashrc. ChainDrop is the proof-of-concept that the attackers already know it.
The C2 that doesn't need a domain
ChainDrop's command-and-control is worth understanding because it defeats domain-blocklist defenses entirely. Instead of a hardcoded server, the payload calls an Ethereum smart contract — 0xE1f2395ee43e45A1556EC6438a88c31B83493103, function selector 0x53ed5143 — querying up to 75 public RPC endpoints in sequence. The contract returns an AES-256-GCM-encrypted domain list; at analysis time it resolved to npm-cache[.]com:443/router. This is the same EtherHiding technique we dissected with ACR Stealer: the takedown-resistant dead-drop is a blockchain read, and rotating C2 costs the operator one cheap transaction.
Exfiltration is gzip → JSON → AES-256-GCM → RSA-OAEP-SHA256, POSTed to /router. There's a signed-GitHub-commit fallback keyed on the marker thebeautifulmarchoftime. None of this touches a filename or hash your EDR has a signature for. What it does touch is the wire.
Remediation
Treat any workstation or CI runner that installed an affected version as credential-exposed, not merely "needs an update." A complete runbook:
1. Am I affected?
Check installs and lockfiles in the exposure window (4 August 2026, ~09:35–13:20 UTC):
# any dependency resolving to a compromised patch, incl. transitive
npm ls keyv flat-cache file-entry-cache cacheable-request cache-manager 2>/dev/null
# scan node_modules for the worm's own artifacts
find . -path '*/node_modules/*' \( -name 'setup.mjs' -o -name 'Math_*.js' -o -name 'math_init.js' \) 2>/dev/null
# repo-level persistence the worm plants
git log --all --diff-filter=A -- '.claude/settings.json' '.vscode/tasks.json'
Also grep your CI caches and golden runner images — a poisoned tarball written to a shared cache reinfects every build after it.
2. Patch — there is no CVE, there is a version floor
This is not a vulnerability with a fixed release; it's compromised versions of legitimate packages. Pin to the last known-good version published before 09:35Z on 4 August (for example [email protected], [email protected], [email protected]) using npm overrides, then reinstall clean:
npm install --ignore-scripts
Upgrade to npm CLI v12+ and enable min-release-age — v12 blocks unapproved lifecycle scripts by default and would have neutralized the preinstall trigger outright. Earlier npm remains exposed.
3. Can't rebuild everything now? — compensating controls
- Set
ignore-scripts=truein.npmrcorg-wide until you've triaged. - Enforce a dependency cooldown (
min-release-age) so a package minutes old cannot enter a build. - Require review/approval on GitHub Actions trusted-publishing workflows and protected environments — this is the propagation channel.
4. Hunt for compromise (MITRE ATT&CK)
| Signal | Technique |
|---|---|
node setup.mjs from a preinstall, then a Bun download from oven-sh/bun during install |
T1195.002 Supply-Chain Compromise · T1059.007 |
Reads of .aws, .ssh, .claude/.cursor/.openai auth files; /proc/<pid>/mem scraping of runner secrets |
T1552.001 Credentials in Files · T1528 Steal Access Token |
New .claude/settings.json SessionStart hook or .vscode/tasks.json runOn: folderOpen on a repo |
T1547.010 Startup Persistence |
Outbound to Ethereum RPC endpoints (dozens, in sequence), then npm-cache[.]com:443/router |
T1041 / T1567 Exfil over C2 / web service |
gh-token-monitor.sh + a new systemd service or macOS LaunchAgent polling api.github.com/user every 60s |
T1098 Account Manipulation |
Scope the credential-exfil look-back to your infostealer history, not just this week — the behaviour predates ChainDrop.
5. Eradicate and verify — order matters
The worm installs a token-monitor with a destructive handler that fires when the GitHub token it watches is revoked. So:
- Remove the token monitor first (
~/.local/bin/gh-token-monitor.shand its service/LaunchAgent), before you touch any credential. - Then rotate — every npm token, GitHub PAT and OIDC-linked secret, cloud key, Kubernetes service account, and Vault credential present on the host during install. Rotate from a clean machine.
- Revoke the AI-assistant API keys that lived in
.claude/.cursor/.openai/.anthropic— assume they are burned. - Purge npm/yarn caches and rebuild affected projects and base images from a known-good baseline.
- Verify by absence: confirm the compromised hashes are gone from every cache and artifact store, and that no
.claude/.vscodepersistence commit survives on any branch.
Where Zero Hunt fits
Every static gate ChainDrop was supposed to trip — signature valid, provenance attested, package name unchanged — passed. The one thing the worm could not fake is its behaviour on the wire: a node process during npm install reaching out to dozens of Ethereum RPC endpoints, then to a domain your network has never spoken to, to POST an RSA-wrapped blob. That is a traffic anomaly long before it is a known IOC.
Zero Hunt's AI Traffic Analysis pillar is built for exactly this window. A proprietary deep-learning model — four parallel inference heads (suspicious traffic, malware classification, attack-type identification, application fingerprinting), trained on billions of PCAP sequences and running locally on the appliance GPU at 2.7+ Gbit/s — flags the sequential RPC fan-out and the never-seen-ASN egress while the install is happening, not in the next morning's SIEM digest. It sees C2 that has no domain and exfiltration that has no signature, because it scores the shape of the session, not a blocklist.
The offensive side closes the loop. Zero Hunt's 10-agent generative swarm can be pointed at your own release path to answer the question ChainDrop asked of everyone else: is our trusted-publishing workflow abusable, and would a stolen maintainer token propagate? Every exploit is generated per-environment by a local LLM, backtested in the AI Gym before it runs, and ECDSA-signed per finding — so the answer is evidence, not an estimate. Provenance told you where a package was built. Neither of these tells you it was authorized; the traffic and the test do.