Shai‑Hulud 2.0: an IR playbook for the npm preinstall worm
CERT-FR issued a bulletin on November 27, 2025 describing an active supply-chain campaign against npm packages that began on November 23, 2025, has affected 700+ packages, executes via a preinstall script to harvest secrets, self-propagate, and in some cases destroy user data; they advise hunting for bun_environment.js, auditing installed versions, temporarily freezing updates, and rotating exposed secrets. (cert.ssi.gouv.fr)
Intrusion Flow
- Initial execution (developer laptop or CI): Compromised npm package versions add a package.json preinstall that runs setup_bun.js, which installs or locates the Bun runtime and launches a large payload bun_environment.js. (wiz.io)
- Credential theft and exfiltration: The payload enumerates environment variables and config files and invokes TruffleHog to capture credentials, then exfiltrates to attacker-created GitHub repositories often labeled with Shai-Hulud-themed descriptions; cross-victim exfiltration has been observed. (cert.ssi.gouv.fr)
- Persistence and remote control: The malware registers a self-hosted runner named “SHA1HULUD” and drops a .github/workflows/discussion.yaml workflow so that creating a GitHub Discussion executes arbitrary commands on the victim’s self-hosted runner; a formatter_*.yml workflow is used to collect GitHub Actions secrets into actionsSecrets.json. (wiz.io)
- Self-replication: Stolen npm tokens and GitHub access allow the actor to publish trojanized versions of additional packages under compromised maintainers, driving rapid worm-like spread across ecosystems. (wiz.io)
- Destructive fallback: Analyses report a “dead man’s switch” that may delete user data (e.g., the home directory) when the malware cannot exfiltrate or persist; CERT-FR also warns about possible user-data deletion. (about.gitlab.com)
Key Artifacts to Pull
- Project and dependency state:
- package.json, package-lock.json, yarn.lock, pnpm-lock.yaml (to diff against clean versions announced by vendors such as Postman and PostHog). (blog.postman.com)
- On-disk indicators:
- GitHub infrastructure state:
- Presence of self-hosted runners named “SHA1HULUD.” Use the GitHub REST API to list runners at org/repo scope during triage. (wiz.io)
- Package manager caches and global install context:
- npm cache directory (default on POSIX: ~/.npm with _cacache) for corroborating when/what versions were fetched. (docs.npmjs.com)
Example triage commands (read-only where possible):