Shai-Hulud: the npm worm that copies itself into your other packages
On September 15, 2025, a self-replicating worm appeared on npm. It stole credentials, used them to publish more malicious packages, and named its exfiltration repos after the sandworms from Dune.
TL;DR
On September 15, 2025, Unit 42 published the first analysis of a novel self-replicating worm spreading through npm. It runs at install time, harvests credentials from the developer machine, uploads them to public GitHub repositories named “Shai-Hulud”, and uses any stolen npm tokens to publish copies of itself into the victim’s other packages. As of this post, several hundred packages are confirmed compromised across multiple maintainer accounts, and the count is climbing.
This is the first npm supply-chain incident I have seen where the malicious code propagates itself. Previous compromises required an attacker to manually compromise each maintainer account. Shai-Hulud automates the next step.
What happened
According to Unit 42’s initial breakdown and follow-up analyses from Wiz, Sysdig, and ReversingLabs, the worm follows roughly this pattern:
- Initial infection: an attacker compromises one or more npm maintainer accounts (the original vector is still being investigated; phishing, stolen tokens, and credential-stuffing are all plausible).
- Malicious version publish: the attacker publishes a new version of a legitimate package controlled by the compromised account. The version includes a
postinstallscript. - Install-time payload: when a developer (or CI runner) installs the package, the
postinstallscript executes. It scans the machine for credentials in~/.npmrc,~/.aws/credentials,~/.ssh/, environment variables, and Git config. - Exfiltration: the harvested credentials are pushed to a newly-created public GitHub repository under the attacker’s control. The repository name pattern includes “Shai-Hulud”, which is how researchers correlated the incidents.
- Self-propagation: if the harvested credentials include an npm token with publish rights, the worm uses that token to publish a new malicious version into every other package the victim maintains. Each of those packages becomes a fresh infection vector.
Step 5 is what makes this novel. The worm does not need a botnet, a C2 server, or a human in the loop. Every successful compromise expands the attack surface by exactly the number of packages the victim maintained.
Why “Shai-Hulud”
The name comes from the sandworms in Frank Herbert’s Dune novels. The attackers chose it for the exfiltration repository naming pattern. Calling the incident by that name is now standard across the security press, so I am using it here too. It is not a flattering name and was clearly chosen by the attackers to be memorable, which is in their interest, not ours, but renaming it after the fact would only confuse readers cross-referencing analyses.
What defenses worked
The usual controls only partly helped. npm 2FA does not stop a stolen automation token. Trusted publishing would reduce that token exposure, but adoption was still early when this hit. Provenance can tell you who published a version; it does not tell you whether that publisher’s token was stolen. npm audit and advisory databases are useful after the fact, not during the first install window.
What might actually help
Two ideas come up repeatedly across the analyses I have read:
Detection time and delayed installs
Socket, Snyk, Sysdig, and others detected the first malicious packages within hours. That is good, but it still leaves the machines installing during those hours. A release-age cooldown closes that specific gap: the package manager refuses versions newer than N days, so detection gets time to catch up before your machine sees the version.
Several package managers were already starting to support this natively. pnpm had a minimumReleaseAge setting. bun had install.minimumReleaseAge. uv had exclude-newer. npm did not yet have an equivalent when this post was written, but there was an open RFC for one.
A four-day cooldown does not prevent supply-chain attacks. It puts you outside the window during which the malicious version is reachable. The cost is that legitimate new releases are delayed too, so urgent fixes need an explicit override.
What this changes
Before Shai-Hulud, the working mental model for npm supply-chain attacks was: an attacker compromises one maintainer, publishes one malicious version, security press picks it up within hours, npm pulls it, the world moves on. The damage is bounded by the popularity of the one compromised package, the duration of the active window, and the spread of automated detection tooling.
After Shai-Hulud, the mental model has to account for cascading compromise: every successful infection that pulls an automation token expands the attack to every other package that token can publish. If a popular tooling-side maintainer is compromised, the worm jumps to dozens of packages in their portfolio in minutes. Some of those packages will themselves have tooling maintainers whose tokens get harvested in the next wave.
The accelerant here is automation. The attackers wrote the worm to skip steps 2-5 of the manual exploitation playbook. We should expect copycats and variants. The defenses that depend on signature verification or known-bad lists work less well against a moving target.
I do not think there is a single defense that solves this. A layered posture seems to be the only honest answer:
- Audit your maintainer accounts for tokens with publish scope. Rotate any that are not actively needed.
- Move to trusted publishing with OIDC where you can. Short-lived tokens reduce the value of any one theft.
- Delay fresh installs in your CI and on workstations, so the detection wave has time to catch up before you pull a freshly-published version.
- Treat install-time scripts as hostile by default.
--ignore-scriptsis one of the better defaults you can set, even if some packages need an opt-in for legitimate scripts.
I am going to keep watching this one. My guess is that the original infection vector will be less interesting than the worm itself: phishing or a stolen token, most likely. The worm code is the part that matters. Whoever wrote it is going to write the next one too.