Shai-Hulud: the first self-propagating npm worm (2025), explained
A worm, not just a bad package
Most npm supply-chain incidents follow a familiar shape: one package gets compromised, ships malicious code, and the damage is contained to whoever installed that package. Shai-Hulud, first seen in 2025, was unsettling for a different reason. It did not wait for an attacker to compromise the next package — it did that itself. That is what made it a worm.
How it spread
The self-propagation loop was the whole story:
- It ran on install. The malicious code executed via an install script when a developer or CI system added or updated an affected package.
- It harvested credentials. Once running, it scraped the environment for secrets — cloud credentials, CI tokens, and crucially npm publish tokens.
- It republished itself. If it found a token that could publish packages, it used that token to inject the same malicious payload into other packages the victim maintained, then pushed new versions.
- It repeated. Each newly poisoned package could compromise the next maintainer who installed it, and the cycle continued without the original attacker lifting a finger.
That last point is what separates a worm from an ordinary compromise: the spread is automatic, riding maintainer trust from one package to the next.
Why install scripts are the pivot
The mechanism everything hinged on is mundane: npm packages can run scripts automatically when installed. That feature is legitimate and widely used, but it means simply adding a dependency can execute arbitrary code on your machine or in your pipeline — before you ever import or run anything. A worm needs exactly that foothold, and the ecosystem hands it over by default.
This is normal ecosystem behaviour being abused, not a sign npm is uniquely broken. Every package manager that runs install hooks shares the exposure.
How to reduce your exposure
You cannot personally audit every transitive dependency, but you can shrink the blast radius:
- Commit a lockfile and pin versions. Reproducible installs mean you do not silently pull a freshly poisoned version.
- Be deliberate about install scripts. Where your tooling allows, install with scripts disabled and enable them only for packages that genuinely need them.
- Scope and shorten tokens. Use least-privilege, short-lived npm and CI tokens, and require 2FA on publish. A stolen token that cannot publish breaks the worm's loop.
- Isolate CI secrets. Keep publish credentials out of environments that run untrusted install steps.
- Update deliberately, not reflexively. Watch for advisories and avoid auto-upgrading to brand-new versions the moment they land.
Where a scan helps
A worm like this often ends with the real prize: secrets that leak out of a build and into something public. An automated scan checks your live site for exposed keys, tokens and files that should never have shipped — the downstream symptom of a supply-chain compromise. Scan your site and see what is reachable.