Skip to content

Build Reports disagree2 publishers 3 min read Published Updated

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

Send a correction

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
Not scored
Hype gap+15
Incentives
Not scored
Confidence58
Why these scores

Claim ledger

Ranked by verification strength, evidence, and original report placement.

  1. [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.

    Reported SupportedView cited source
  2. [2]

    The payload downloads Trufflehog, a legitimate open source secret-scanning tool, and uses it to confirm the validity of any credentials it finds.

    Reported SupportedView cited source
  3. [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.

    Reported SupportedView cited source
  4. [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.

    Reported SupportedView cited source
  5. [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.

    Reported SupportedView cited source
  6. [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.

    Reported SupportedView cited source
  7. [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.

    Reported SupportedView cited source
  8. [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.

    Reported Supported Source: Palo Alto Networks Unit 42, as reported by The New StackView cited source
  9. [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.

    Reported SupportedView cited source
  10. [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.

    Reported SupportedView cited source
  11. [11]

    ChainDrop's command infrastructure was parked inside an Ethereum smart contract, rendering domain blocklists moot.

    Reported SupportedView cited source
  12. [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.

    Reported SupportedView cited source
  13. [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.

    Derived Supported
  14. [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.

    Derived Supported
  15. [15]

    ChainDrop's reported rate is better than 100 packages compromised per hour.

    Derived Supported
  16. [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.

    Reported ContestedView cited source

Sources

2 independent publishers whose own reporting we read for this story.

  1. dev.to

    1 article · September 1, 2026

    The npm Worm That Brought Its Own Interpreter
  2. thenewstack.io

    1 article · August 31, 2026

    Shai-Hulud: Whoever controls your package registry controls your pipeline

Topics and entities