The attack exploited two compounding structural properties of the npm ecosystem. First, npm's lifecycle hook system allows packages to execute arbitrary shell code (via postinstall/preinstall scripts) during installation, before any human reviews the code. Second, npm's publishing model assigns long-lived, scoped tokens with broad publish permissions — tokens that, once harvested from a developer's environment, can be used to publish new versions of any package in that maintainer's account. The worm did not require any technical vulnerability in npm's infrastructure. It required only that one developer's machine run an infected package, exposing their credentials to a harvester that already knew exactly what to do with them.
npm and GitHub's security teams quarantined hundreds of malicious package versions across multiple response waves. CISA issued a formal advisory urging immediate credential rotation and adoption of OIDC-based Trusted Publishing to replace long-lived tokens. The Shai-Hulud campaign directly accelerated npm's move toward requiring 2FA for high-impact maintainers and toward SLSA provenance attestations for published packages — hardening measures that the 2026 Mini Shai-Hulud campaign would subsequently render insufficient by attacking the trusted build runners themselves.
The Museum Placard
In September 2025, a malicious npm package did something packages rarely do: it looked at its environment, found credentials, and went looking for more packages to infect.
The worm that security researchers named Shai-Hulud — after the colossal sandworms of Dune, which consume everything in their path and leave the desert changed — did not exploit a vulnerability in npm's servers, or a bug in Node.js, or a misconfiguration in GitHub Actions. It exploited the design of the ecosystem itself. The infection spread through the trust graph of the npm registry: from one maintainer's stolen token, to every package they maintained, to every user of those packages, to every set of credentials those users possessed.
The blast radius was not bounded by the original infected package. It was bounded only by the npm social graph.
---
How the Worm Ate
Shai-Hulud's initial infection vector was deceptively simple: a malicious package published to npm containing a postinstall lifecycle script. When any developer or CI runner installed that package, npm executed the script automatically — before any human had seen the code, before any linter had run, before any sandbox applied.
The script did three things:
1. Credential Harvest. It scanned the environment for secrets: npm authentication tokens (stored in ~/.npmrc), GitHub Personal Access Tokens (~/.gitconfig, environment variables, CI runner secrets), AWS credentials (~/.aws/credentials), GCP service account keys, Azure tokens, Kubernetes kubeconfig files. Modern developer machines and CI environments are credential-dense. A single infected install on a well-configured developer's workstation might yield a dozen distinct secrets across multiple services.
2. Exfiltration. Harvested credentials were committed to attacker-controlled GitHub repositories using the victim's own GitHub token — an approach that made the exfiltration appear as legitimate git activity, and that used GitHub's own infrastructure as the command-and-control channel. Some variants committed stolen secrets under other victims' GitHub accounts, making attribution and cleanup substantially harder.
3. Self-Replication. Using the harvested npm token, the malware queried the npm registry to enumerate every package the victim maintained, then published a new version of each — versions that contained the same malicious postinstall script. Every package the infected developer owned became a new infection vector. Every downstream user of any of those packages would, on their next npm install, run the payload.
This is the recursive structure that earns the name "worm": the infection propagated not linearly (attacker publishes one bad package) but exponentially (each victim becomes a new publisher of malicious packages to their own downstream users).
---
Shai-Hulud 2.0: The Refinement
The November 2025 iteration shifted from postinstall to preinstall scripts — a subtle but meaningful change. preinstall runs before the package contents are extracted, before dependency resolution completes, before any security tooling that inspects installed files has a chance to run. By executing at the earliest possible point in the install lifecycle, 2.0 reduced the window for detection and increased the probability of running before any security agent could intervene.
The 2.0 variant also introduced a behavioral camouflage technique: the malicious file was named setup_bun.js — a name that, in 2025, plausibly suggested legitimate Bun runtime configuration. Developers and automated scanners who saw setup_bun.js in a node module directory had a plausible, benign explanation available. Many did not look further.
The exfiltration mechanism was also refined. Rather than committing credentials to a single attacker-controlled repository, 2.0 distributed stolen secrets across multiple GitHub accounts using a rotation scheme — spreading the trail across a larger surface and complicating incident response.
---
The Six Laws at Work
Law III — Transitive Trust
No developer who installed a Shai-Hulud-infected package intended to run malicious code. They installed a package they had used before, or a dependency of a framework they trusted, or a library recommended in a tutorial. The malicious payload was not in the code they audited. It was two or three hops upstream, in a package they had not heard of, maintained by a developer whose credentials had been compromised in a prior wave of the infection.
Transitive trust is not a vulnerability anyone introduced. It is the load-bearing assumption of package management itself: that the packages your dependencies depend on are safe. Shai-Hulud demonstrated that this assumption transforms the npm dependency graph into a propagation network.
Law II — Ambient Authority
The npm lifecycle hook system grants postinstall scripts the full authority of the user who runs npm install. On a developer's machine, that is the developer's own credentials, filesystem, and environment. In a CI/CD pipeline, that is the pipeline's service account, with whatever IAM permissions the pipeline uses to deploy software.
The script does not need to escalate privileges. It does not need to exploit a sandbox escape. The authority is ambient — granted automatically to every package that declares a lifecycle hook, because the ecosystem was designed assuming that package authors are trustworthy.
Law I — Boundary Collapse
The npm install process was not designed to be a trust boundary. It was designed to be a convenience mechanism. But in practice, npm install became a boundary-crossing event: code from the internet executing with local credentials. The boundary between "fetching a package" and "executing untrusted code with full credential access" was collapsed — not by an attacker exploiting a weakness, but by the original design intent of the lifecycle hook feature.
Law 0 — Katie's Law
The npm registry is the backbone of the JavaScript ecosystem. It hosts millions of packages depended on by hundreds of millions of developers and billions of end users. Its security architecture for package publishing — long-lived tokens, per-account permissions, no mandatory code review before publication — was designed for a world where the primary threat model was "a developer accidentally breaks their package."
The economics of maintaining that infrastructure, and of requiring maintainers to adopt more secure publishing practices, consistently favored convenience. OIDC-based Trusted Publishing existed before Shai-Hulud. It was not the default. The gap between "this secure practice exists" and "this secure practice is enforced" is where the worm lived.
---
The Propagation Geometry
What makes Shai-Hulud structurally distinct from a conventional supply chain attack (like the Axios compromise) is the geometry of its spread.
A conventional supply chain attack is a directed contamination: one package is compromised, and all downstream users are exposed. The exposure set is the fan-out of that one package.
Shai-Hulud's exposure set is determined by the transitive closure of the npm maintainer graph: every package maintained by every developer whose credentials were harvested in any wave of the infection. When a prolific open source maintainer (who might maintain dozens of packages with millions of combined weekly downloads) is infected, the worm's next-generation exposure is proportionally larger.
The theoretical maximum propagation is the entire npm registry — bounded only by the connectivity of the maintainer graph. No single package vulnerability creates that surface. The worm does, because it moves along the credential layer rather than the dependency layer.
---
What Should Have Stopped This
Mandatory OIDC Trusted Publishing. If npm publishing required OIDC-bound short-lived tokens tied to specific CI/CD workflows, a stolen developer token would be insufficient to publish a new package version. The token would have no publishing authority outside the workflow it was issued for. This was the primary structural fix the incident demonstrated was necessary — and which Mini Shai-Hulud (2026) would subsequently route around at the OIDC layer itself.
Lifecycle hook sandboxing or opt-in. postinstall and preinstall scripts are opt-in for package publishers but not for package consumers. A package that declares a lifecycle hook runs that hook on every downstream machine that installs it. Inverting this — requiring consumers to explicitly opt in to executing lifecycle scripts — would contain the blast radius of any individual compromised package to users who had deliberately enabled script execution.
Real-time behavioral analysis of npm publications. Static analysis of packages at publication time catches known-malicious patterns but not novel obfuscation. Behavioral analysis — running newly published packages in an isolated environment and observing their install-time behavior — would detect credential harvesting regardless of how the harvester is obfuscated. npm introduced enhanced automated scanning post-Shai-Hulud, but behavioral sandboxing at publication time remained resource-constrained.
Credential rotation automation. The dwell time between infection and credential rotation was, for most victims, measured in hours to days. Automated credential rotation — triggered by anomalous token usage detected by the registry — would reduce the window during which harvested credentials could be used to publish further malicious versions.
---
Curator's Note
Shai-Hulud is not a story about a clever attacker finding an obscure bug. It is a story about a design becoming a weapon.
The npm lifecycle hook system was designed to make package installation convenient: install a package, get a configured environment. The npm token system was designed to make package publishing frictionless: authenticate once, publish many. The semver range convention was designed to make dependency management low-maintenance: let the ecosystem deliver updates automatically.
Each of these design decisions was reasonable in isolation. Composed together, they produced a system where a single compromised credential could silently propagate malicious code to an unbounded number of downstream users, using infrastructure that looked entirely legitimate, through a process that the ecosystem told developers was safe to trust.
The worm didn't break the ecosystem. It read the documentation and followed it exactly.
EFFODE · LEGE · INTELLEGE