Breach report
npm's Shai-Hulud: phished maintainers, poisoned chalk and debug, a self-spreading worm
What happened
September 2025 brought two linked supply-chain attacks on the npm ecosystem. On September 8, 2025, malicious versions of 18 packages were published after a phishing email reached maintainer Josh Junon from [email protected], a lookalike domain registered just three days earlier. The poisoned packages included chalk (about 300 million weekly downloads) and debug (about 358 million), totaling over two billion downloads per week. The injected code was a browser-based crypto clipper that hooked fetch, XMLHttpRequest, and wallet APIs to silently rewrite cryptocurrency payment addresses across Ethereum, Bitcoin, Solana, and other chains.
A week later, around September 15–16, 2025, a second and more dangerous attack — nicknamed Shai-Hulud — hit over 100 packages. Unlike the clipper, Shai-Hulud was a self-propagating worm.
Root cause
Both waves were embedded malicious code (CWE-506) delivered through the software supply chain. The chalk/debug compromise turned on a single phished maintainer credential. Shai-Hulud then automated the spread: its payload ran the TruffleHog secret scanner to find tokens and cloud keys in the build environment, and when it discovered additional npm tokens it "will automatically publish malicious versions of any packages it can access." Stolen GitHub secrets were exfiltrated into attacker-created public repositories.
How it would have been caught
Pinning dependencies to known-good versions with integrity hashes prevents a freshly published malicious version from being pulled silently. Behavioral scanning of new package versions flags a package that suddenly hooks wallet APIs or scans for secrets. A reproduction installs the poisoned version in a sandbox seeded with a canary token and confirms the token is harvested and exfiltrated.
How to prevent it
- Enforce phishing-resistant MFA (hardware keys) on all package-maintainer accounts.
- Pin dependencies with a lockfile and integrity hashes; disable install-time scripts where possible.
- Vet new versions before adoption rather than auto-upgrading; treat sudden publishes with suspicion.
- Scope and short-live CI tokens so a compromised build cannot republish packages.
The Breachwire test (red → green)
Install the poisoned package version in a sandbox holding a canary npm/GitHub token and confirm the malware harvests and exfiltrates the token (and rewrites a test wallet address) — the RED control. Pin to a clean version with integrity verification and disable install scripts, reinstall, and confirm the canary is never read or exfiltrated, while the package's legitimate functionality still works.