Shai-Hulud: the first self-replicating npm worm
In autumn 2025 the Shai-Hulud worm infected hundreds of npm packages, spreading itself. We analyse the supply chain attack and how to secure your pipeline.
In September 2025, the npm ecosystem — the heart of nearly every modern web application — experienced something not seen at this scale before: a self-replicating worm. Named Shai-Hulud (after the sandworms of “Dune”), the malicious code not only infected packages but spread itself — using stolen developer tokens to publish infected versions of other packages those developers maintained. Within hours the number of compromised packages ran into the hundreds. It’s a textbook, terrifyingly effective software supply chain attack.
How the worm worked
Shai-Hulud’s mechanics combined several known techniques into an automated loop:
- Entry through a compromised account. The starting point was compromising a popular package maintainer’s account (usually via phishing for npm credentials or a stolen CI token).
- A malicious install script. The infected package version contained a script run automatically on install (
postinstall) that executed the main logic on the victim’s machine — a developer’s computer or, worse, a CI/CD environment. - Secret theft. The script searched the environment for tokens: npm, GitHub, cloud keys (AWS, GCP), environment variables. It exfiltrated the stolen data, often even publishing it to public repositories.
- Self-replication. And here’s the crux: with the victim’s npm token, the worm automatically published infected versions of other packages that victim maintained. Each new infection became a source of more — hence “worm”, not just a malicious package.
The snowball effect meant that an attack from a single compromised account spread across the entire dependency graph faster than manual response could stop it.
Why it’s so dangerous
Shai-Hulud struck the foundation of modern software development: trust in dependencies. A typical Node.js application has hundreds — and indirectly thousands — of dependencies; nobody reads their code on every update. Installing a package, we trust that its author is who they claim to be and that the version we pull is the one they published. The worm broke both promises at once.
The CI/CD vector was especially dangerous. Build environments usually hold the broadest permissions in the whole organisation: cloud tokens, deployment keys, registry access. A malicious postinstall run in a pipeline isn’t an “infected laptop” but potentially the keys to the entire production environment. It’s the same pattern we described in supply chain attacks — here taken to an extreme by automation.
How to defend: practical steps
You can’t protect against every future npm infection, but you can drastically limit the impact:
Pin versions and use a lockfile. Installing with exactly locked versions (npm ci instead of npm install, a lockfile in the repo) means a new malicious version won’t jump in automatically. Consider delaying adoption of new versions by a few days — many malicious packages are detected and removed within that window.
Restrict install scripts. npm install --ignore-scripts where possible blocks the most common malicious-code execution vector. In CI this matters especially.
Least privilege in CI/CD. A pipeline shouldn’t hold tokens broader than a specific job requires. Short-lived tokens, separate for build and deploy, with no production access from the dependency-install stage. It’s the same least-privilege principle as with AI agents.
Rotate secrets after an incident. If you could have been in the exposure window, assume token leakage and rotate them. Secret theft defeats “fixing” — exactly as with ToolShell.
MFA on maintainer accounts and the registry. The entry point was a compromised account. Phishing-resistant MFA on npm/GitHub and automation tokens raises the bar at the source.
Dependency inventory (SBOM) and scanning. Knowing what you actually pull in, plus automated scanning for known malicious packages, is part of vulnerability management extended to the supply chain.
Frequently asked questions (FAQ)
We use npm — so were we at risk? Potentially yes, if during the attack window you installed fresh dependency versions without locked versions or with scripts running in CI. Check whether any infected package (or its dependency) reached your builds in that period, and whether pipeline tokens leaked.
How did Shai-Hulud differ from an ordinary malicious package? Self-replication. A classic malicious package has to be downloaded to do harm. Shai-Hulud published infected versions of further packages itself, using victims’ stolen tokens — so it spread exponentially, like a network worm of old, but in a code ecosystem.
How do we check whether our tokens leaked? Review npm publish logs and GitHub/cloud token activity from the exposure period, look for unauthorised package publications and unusual key usage. If in doubt — rotate everything that could have been within reach of a script in CI.
Does --ignore-scripts break installation?
Some packages legitimately use install scripts (compiling native modules), so a global switch-off may need adjustments. A sensible compromise: disable scripts by default and make an explicit exception for known, trusted packages that require compilation.
How do we test our pipeline’s resilience? A security audit covering CI/CD checks token scopes, build environment isolation and malicious-dependency risk — exactly the conditions that decided Shai-Hulud’s scale. Get in touch if you build software and want to verify this.
Summary
Shai-Hulud is a harbinger of a new class of threats: automated, self-replicating supply chain attacks that spread faster than you can respond manually. The defence isn’t reading the code of every dependency but limiting the impact: locked versions, disabled scripts in CI, least-privilege pipelines and fast secret rotation. If you build software, treat this attack as a warning and check your pipeline before the next worm does.
Sources and further reading: GitHub Security.