In September 2025, a piece of npm malware did something most supply chain attacks never manage: it spread on its own. Dubbed Shai-Hulud by the researchers who first flagged it, the worm compromised a handful of popular packages, then used credentials it stole from infected machines to automatically republish itself into every other package its victims controlled — no attacker keystrokes required after the first infection. Within roughly 48 hours, teams at Socket, StepSecurity, and Aikido were tracking infections across more than 500 npm packages tied to over 100 maintainer accounts, including packages published by well-known security and DevOps vendors. This wasn't a one-off malicious update. It was a self-replicating chain reaction, and it exposed a structural weakness in how open source registries handle trust, automation, and publishing permissions. Here's how these worms work, why npm is a uniquely good breeding ground for them, and what actually stops the spread.
What Makes a Malicious Package Actually a "Worm"?
A worm is malware that propagates itself without further attacker action, and that's exactly what separated Shai-Hulud from ordinary supply chain compromises. Most npm attacks — the 2018 event-stream backdoor, the 2021 ua-parser-js hijack, the 2022 node-ipc protestware — involved a single compromised package or maintainer account, requiring the attacker to manually target each new victim. Shai-Hulud instead shipped a payload that, once it ran on a developer's or CI machine, harvested that machine's own npm publishing tokens and used them to push infected versions of every package the victim was authorized to publish. Each newly infected package became a new launch point, infecting the next set of maintainers automatically. It's the same self-replication logic as the 1988 Morris worm, just repurposed for npm install instead of Unix sendmail — and it's why a handful of initial infections ballooned into hundreds of packages in under two days.
How Did the Shai-Hulud Worm Actually Spread Through npm?
It spread by weaponizing the postinstall lifecycle hook that npm runs automatically on every install, turning a routine dependency update into an infection vector with zero user interaction. When a developer or CI pipeline ran npm install on an already-compromised package, npm silently executed a bundled script (widely reported as bundle.js) before the developer ever touched the code. That script fingerprinted the host, downloaded and ran a copy of the open source secret-scanning tool TruffleHog against the local filesystem and environment variables, and searched specifically for npm auth tokens, GitHub personal access tokens, and cloud credentials (AWS and GCP keys were both targeted). Any npm token it found was immediately used to npm publish trojanized versions of every package that token had write access to — often dozens of packages per compromised maintainer, since many developers hold publish rights across an entire GitHub organization's package scope.
What Did the Worm Do Once It Had Access?
Once it had valid credentials, it didn't just republish itself — it exfiltrated data and dug in for persistence, turning each compromised machine into a beachhead rather than a one-time hit. Stolen secrets were pushed to attacker-controlled infrastructure, and in a notably brazen move, the worm created public GitHub repositories under each victim's own account — typically named "Shai-Hulud" — to store exfiltrated data in plain sight, disguised as legitimate-looking repos. On systems with GitHub Actions access, it also modified or injected CI/CD workflow files, giving the attacker a foothold that could survive even after the original malicious package version was pulled from the registry. That combination — credential theft, self-propagation, and CI persistence — is what pushed npm's security team and GitHub to treat this as an active incident rather than routine malware cleanup, pulling infected package versions and revoking exposed tokens as reports came in.
Why Is npm Specifically Vulnerable to This Kind of Attack?
npm is vulnerable because its trust model was built for a smaller, friendlier ecosystem and never fully caught up to the scale of modern JavaScript dependency trees. Three structural factors compound the risk. First, lifecycle scripts like postinstall execute arbitrary code by default on install, with no sandboxing — a design decision from npm's early days that most developers don't even realize is active. Second, the average modern JavaScript project pulls in hundreds of transitive dependencies, meaning a single compromised leaf package can reach deep into unrelated codebases; audits following Shai-Hulud found some organizations were exposed through four or five layers of indirect dependency. Third, npm tokens are frequently over-scoped and long-lived — a token minted for one project often has publish rights across a maintainer's or organization's entire package portfolio, which is precisely the property the worm exploited to jump from one infected package to dozens of others in a single automated step.
Has This Happened Before, and Will It Happen Again?
Yes, and the pattern is accelerating rather than fading, which is why Shai-Hulud is treated as a template rather than an isolated incident. Earlier npm incidents like the 2018 eslint-scope account takeover and the 2021 coa/rc package hijacks showed attackers the value of registry-level compromise, but those relied on manual redistribution. Shai-Hulud automated that last step. Security researchers subsequently observed follow-on campaigns reusing the same secret-harvesting-and-republish playbook against different maintainer pools, suggesting the technique — not just the specific malware — is now part of the standard supply chain attacker toolkit. Given that npm hosts several million packages and installs happen billions of times per week globally, even a low success rate per compromised token produces a fast-moving, high-volume incident once self-propagation is added to the mix.
How Can Teams Actually Defend Against Self-Propagating Package Worms?
Defense works by breaking the propagation loop at multiple points rather than relying on any single control. Concretely, that means: disabling or sandboxing lifecycle scripts during CI installs (npm install --ignore-scripts where feasible); issuing short-lived, narrowly-scoped publish tokens instead of long-lived org-wide credentials; enforcing two-factor authentication and provenance attestation on all npm publishes; pinning dependency versions with lockfiles and verifying package integrity hashes rather than trusting floating ranges; and continuously scanning installed dependencies — not just direct ones — for known-malicious versions and anomalous postinstall behavior. Because worms move faster than manual review cycles, the controls that actually matter are the automated ones that catch a malicious version before npm install ever executes it.
How Safeguard Helps
Safeguard is built for exactly this failure mode: malicious code that spreads faster than a human can review it. Safeguard continuously monitors your full dependency graph — direct and transitive — against real-time threat intelligence on compromised and worm-infected npm packages, flagging affected versions the moment they're identified rather than waiting for a manual CVE writeup. Safeguard's policy engine can block installs that trigger unreviewed postinstall scripts, enforce least-privilege scoping on publish tokens across your organization's repositories, and alert on anomalous outbound network activity from build and CI environments — the exact behavior TruffleHog-style credential harvesters exhibit. When a package in your supply chain is implicated in a worm campaign, Safeguard maps blast radius across every affected repository and pipeline instantly, so teams can revoke tokens and roll back versions before a self-propagating attack has a chance to jump to the next maintainer. In an ecosystem where one compromised token can become five hundred compromised packages in two days, that speed is the difference between an incident and an outbreak.