The Shai-Hulud worm and what it means for npm on Macs
Starting September 15, 2025, a self-propagating npm worm compromised 500+ packages. Developer Macs that ran npm install during the window were in blast radius.
- npm
- supply-chain
- shai-hulud
In mid-September 2025, security vendors began reporting a self-propagating npm malware campaign dubbed Shai-Hulud. Kaspersky's Securelist analysis (published September 16, 2025) documented more than 500 infected packages, including @ctrl/tinycolor with over two million weekly downloads at the time.
This post covers Shai-Hulud specifically. It is a separate incident from the September 8, 2025 phishing compromise of maintainer Josh Junon (qix), which affected 18 high-download packages including chalk and debug with browser-focused crypto-theft payloads. The Register reported that incident on September 8, 2025.
How Shai-Hulud spread
Unit 42 assessed that initial access likely came from a credential-harvesting phishing campaign spoofing npm MFA updates. Once on a maintainer machine, a postinstall script ran a large bundle.js payload that:
- Harvested npm, GitHub, and cloud tokens from the environment
- Used tools like TruffleHog to scan for secrets
- Republished malicious versions across other packages the maintainer controlled
- Created public GitHub repositories (named "Shai-Hulud") to exfiltrate stolen data
Kaspersky noted the worm's destructive actions targeted Linux and macOS hosts in most branches of its logic.
Shai-Hulud 2.0 (November 2025)
A second wave, reported in November 2025 and analyzed by Kaspersky, compromised 800+ additional npm packages. This variant used setup_bun.js and bun_environment.js with a preinstall trigger. Kaspersky reported that if the malware could not exfiltrate data, it could trigger a destructive wipe of files in the home directory.
Why Mac developer laptops matter
Every Mac that ran npm install during a compromise window pulled poisoned versions into node_modules and potentially updated lockfiles. Central artifact scanners help after IOCs are published. Local lockfiles update in real time.
What postinstall bypasses
postinstall and preinstall scripts execute at install time, before runtime MCP policy enforcement. That is why inventory-after-install complements registry takedowns rather than replacing them.
Fleet response pattern
Teams that responded well typically:
- Identified affected lockfiles across developer machines, not just CI
- Rotated npm, GitHub, and cloud tokens
- Established continuous diff so the next compromise surfaces in hours, not quarters
Koban reads npm lockfiles and package.json from enrolled Macs. Fleet rules for unexpected package names or version bumps on heartbeat complement registry-level response.