Shai-Hulud spreads by bumping a version number your dependency range already accepts
September's wave altered more than 500 npm package versions and November's backdoored 796, both by republishing under a fresh version number, which is exactly the thing an exact pin declines to fetch.
The Engineer · Build desk
What happened
- Between September 14 and 18, 2025, more than 500 npm package versions changed with no maintainer running npm publish, each new version carrying the code that published further versions from infected machines.
- Two months later, on November 24, a larger variant backdoored 796 packages, ran earlier in the install process, and deleted the user's home directory when it found neither credentials to steal nor a way to spread.
- Given a stolen npm token, the payload listed every package the maintainer owned, downloaded them, injected itself into the postinstall step, bumped each version number and republished.
- Given a GitHub token, it wrote the harvested credentials to a new public repository and flipped reachable private repositories public under the original name with a "-migration" suffix appended.
Why it matters
- Who has to decide decision Whether your build reads a recorded pin or re-resolves a range is the control that decides if a self-republished version can enter, because no diff ever reaches a reviewer.
- Who's exposed exposure Everything the install shell can read is in scope, cloud instance metadata included, so the credential genuinely at risk is the CI role rather than one developer's token.
- What it limits constraint A pipeline that verifies attestations and stops there has bought assurance about who published and none about what shipped, which narrows what signing can be asked to do.
- What it sets up precedent With coding-agent credentials named as the objective rather than collected incidentally, teams running agents in CI have to treat those tokens as publish-capable secrets.
"Earlier in the install process" is the phrase in that account doing the most work, and it is the one you cannot act on. The New Stack says the November variant moved its execution earlier to render developer triggers irrelevant [16]. It does not name the hook. If the new entry point is still a lifecycle script, installing with scripts disabled covers it. If it is not, that control covers the September payload and not the thing that replaced it. One sentence of a writeup does not settle which, and the difference is the difference between a fix and a speed bump.
The propagation step needs one thing to work downstream: a version number that something will resolve. It bumps the version and republishes under the maintainer's own name [5], so a consumer whose manifest carries a range picks up the poisoned build on its next resolve, while a consumer installing from a recorded exact version does not until someone re-resolves [13]. A lockfile inspects nothing. It declines to select a version that was not recorded when it was written, and that is the whole of its contribution here.
The same account describes the limit case. On August 4, 2026, a variant called ChainDrop compromised more than 400 packages in under four hours by riding a legitimate, cryptographically signed release pipeline, so each poisoned version arrived with a valid SLSA provenance attestation [10]. That is better than 100 packages an hour [15]. Its command channel sat inside an Ethereum smart contract, which renders domain blocklists moot [11]. The attestation was accurate, and that is the problem: provenance answers who built the artifact, not what the artifact does.
Treat 500-plus and 796 as counts from someone else's dependency graph. They are not even the same unit, since September counted package versions altered and November counted packages backdoored [3][16], so dividing them gives 1.59 and means nothing as a growth rate [14]. For either figure to reach your pipeline, one of those names has to sit in your tree, directly or transitively, and your install has to re-resolve a range rather than read a pin.
Two smaller details are worth keeping. The payload downloaded Trufflehog, a legitimate open source secret scanner, to confirm that the credentials it harvested were live [2]; the worm ships with a validation step that some internal secret audits skip. Palo Alto Networks' Unit 42 is moderately confident that parts of the script were written by an LLM, going on comments and emoji embedded in the payload [8].
The generalization in that piece holds up: swap npm publish for terraform apply and the mechanics hardly change [12]. Install-time execution with the permissions of whatever ran the install is a property of registries, not of JavaScript [6]. The worm never has to persuade a reviewer, because it only ever needed one credential and an install script [7].
What to watch
- Whether anyone publishes the specific lifecycle hook the November variant used, since that decides whether disabling install scripts covers it at all.
- Whether npm changes the default so that publishing needs a factor a token in the environment cannot supply.
- Whether consumers of SLSA attestations add content screening on top of signature verification after the ChainDrop case.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence62
- Adoption
- Insufficient
- Hype gap+15
- Incentives
- Insufficient
- Confidence58
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
The compromised package runs a postinstall script (a single file called bundle.js) that searches the infected machine for anything resembling credentials, including npm and GitHub personal access tokens, AWS or GCP secrets, and whatever it can extract from a cloud instance's metadata service.
- [2]
The payload downloads Trufflehog, a legitimate open source secret-scanning tool, and uses it to confirm the validity of any credentials it finds.
- [3]
Beginning September 15, 2025, npm packages started updating themselves with no maintainer running npm publish and no pull request merged; between September 14 and 18, more than 500 package versions were altered, each carrying a payload that published more versions of more packages on more machines without human involvement.
- [4]
On locating a GitHub token, the worm exfiltrates everything to a new public repository and makes any private repositories it can reach public, republished under the original name with a "-migration" suffix appended.
- [5]
If it finds an npm token, the worm calls the registry's API to list every package the compromised developer maintains, downloads them, injects itself into the postinstall step, bumps the version number, and republishes them with no further input.
- [6]
Every package manager and infrastructure registry runs on the same assumption: installing something means running whatever is in the package, sight unseen, with whatever permissions the install process has; that assumption was safe enough when publishing required a person to do it.
- [7]
The worm does not need to convince anyone of anything; it needs one compromised credential and an install script, after which the registry confers trust and every subsequent dependency reinforces it.
- [8]
Palo Alto Networks' Unit 42 is moderately confident that the malicious script was partly written by an LLM, based on stylistic tells such as code comments and emoji embedded in the payload.
- [9]
By spring of 2026, an offspring named Mini Shai-Hulud had evolved from hunting generic developer secrets to specifically targeting credentials belonging to Claude, Codex, Cursor and Gemini, on the reasoning that AI coding tools are involved in the most interesting projects.
- [10]
A variant named ChainDrop appeared on August 4, 2026 and in less than four hours compromised more than 400 packages by riding a legitimate, cryptographically signed release pipeline, which granted each poisoned version a valid SLSA provenance attestation and circumvented the mechanism built to verify that a package had not been tampered with.
- [11]
ChainDrop's command infrastructure was parked inside an Ethereum smart contract, rendering domain blocklists moot.
- [12]
The vulnerability is not specific to npm: trade npm publish for terraform apply and the mechanics hardly change, because a Terraform provider pulled from a public registry is fetched on the same terms as an npm package.
- [13]
Because propagation works by bumping the version number and republishing, an install that re-resolves a version range can select the poisoned build, while an install that reads an already-recorded exact version cannot select it until someone re-resolves.
- [14]
Dividing the November count by the September count gives 1.59, but the two figures measure different units, so the ratio is not a growth rate.
- [15]
ChainDrop's reported rate is better than 100 packages compromised per hour.
- [16]
Two months later, on November 24, a larger variant called Shai-Hulud 2.0 backdoored 796 packages, moved its execution earlier in the install process to render developer triggers irrelevant, and deleted the user's home directory if it could not find credentials to steal or a way to spread.
Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.
Sources
2 independent publishers whose own reporting we read for this story.
- dev.toThe npm Worm That Brought Its Own Interpreter
1 article · September 1, 2026
- thenewstack.ioShai-Hulud: Whoever controls your package registry controls your pipeline
1 article · August 31, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- Software Supply Chain SecurityFollow
- Package install scriptsFollow
- Dependency Pinning And Lock FilesFollow