The Shai-Hulud Worm: A New Cross-Ecosystem Supply Chain Attack
The Shai-Hulud worm, a self-replicating malware that spreads through NPM package registries, has returned with two significant changes: it now crosses from the NPM ecosystem into Go modules, and it has abandoned post-install scripts in favor of IDE configuration files. Both changes make it harder to detect and easier to miss with standard defenses. This article breaks down how the attack works and what you can do to protect your development environment.
How the original worm worked
The original Shai-Hulud operated on a four-stage cycle that turned victims into unwitting distributors.
A developer installs a compromised NPM package, either a typosquatted name or a legitimate package whose maintainer credentials were stolen. The package's post-install script runs immediately and scans the machine for credentials: .npmrc tokens, GitHub tokens, cloud provider keys, and SSH keys. Notably, the worm repurposed TruffleHog, a legitimate secrets-scanning tool, to locate these efficiently.
Once it has an NPM token, the worm publishes poisoned versions of every package the victim maintains, typically as minor patch versions to avoid suspicion. Any developer who then runs npm install or npm update pulls in the infected package, and the cycle starts again on their machine.
What's new in the latest variant
Security firm Socket.dev flagged this new variant in mid-2024. Two changes make it considerably more dangerous than the original.
Cross-ecosystem delivery
The worm now uses Go modules as a delivery vehicle. A Go module called "Verana Blockchain" was found to contain the malicious payload. Go doesn't have post-install scripts, so the malware wasn't embedded in executable Go code. Instead, it was bundled inside the module's source archive, hidden in configuration directories designed to be picked up by editors. A developer downloading the source to inspect or use it would also download the hidden files without any Go tooling flagging the issue.
IDE configuration hooks instead of install scripts
The more significant change is the infection vector. Rather than running during npm install, the malware now runs when a developer opens the project in their editor.
The compromised archive includes two hidden configuration directories:
.vscode/tasks.json, configured to execute onfolderOpen.claude/settings.json, configured to execute onSessionStart
Both trigger a command like node .claude/setup.mjs automatically when the project is opened.
The full attack chain from that point: the editor reads the configuration, the hook fires and runs the setup script, the script decodes and executes the main payload (often using the Bun runtime to obscure the process name), and the payload scrapes credentials from .env files, config files, and environment variables. Before exfiltrating anything, the worm checks for common enterprise security tools like CrowdStrike, Microsoft Defender, or SentinelOne and goes dormant if it finds them. If the coast is clear, stolen credentials are sent to an attacker-controlled server, and any NPM token found is used to continue the replication cycle.
Why common defenses don't stop this
Two widely recommended mitigations are ineffective against this variant.
npm install --ignore-scripts prevents pre-install and post-install scripts from running during installation. The new attack doesn't trigger during installation at all. It triggers when the project is opened in an editor, making this flag irrelevant against this vector.
Running npm uninstall after discovering a malicious package removes it from node_modules but leaves the .vscode/tasks.json and .claude/settings.json files in place. Those files aren't part of the NPM package; they were written to the project directory and persist independently. The malware survives uninstallation and runs again the next time the project is opened.
How to protect your environment
Inspect project configuration files before opening
Before opening any newly cloned or downloaded project in your IDE, check its contents from the terminal. List all files including hidden ones:
Look for .vscode/, .claude/, or .github/workflows/ directories. If you find any, open the configuration files inside them in a plain text editor that won't execute hooks, and read through any commands they define. Pay particular attention to tasks or hooks set to run on folderOpen or SessionStart. If anything looks unfamiliar or suspicious, delete the configuration directory before opening the project in your IDE.
Treat these configuration directories the same way you would an unknown binary. They can execute arbitrary code with your user's permissions.
Disable automatic tasks in VS Code
VS Code can be configured to block automatic task execution, requiring your explicit approval before running any task from a newly opened folder.
Open the command palette with Cmd+Shift+P on macOS or Ctrl+Shift+P on Windows and Linux, search for Tasks: Manage Automatic Tasks, and select Disallow Automatic Tasks. VS Code will still detect configured tasks but will not run them without manual confirmation.
Use short-lived tokens and OIDC
The worm's replication depends on stealing long-lived static credentials. Reducing how long credentials remain valid limits the damage if they're stolen.
Configure personal access tokens with short expiration windows, such as 24 hours or 7 days, and scope them to the minimum permissions required. A token used for reading packages shouldn't have publishing rights.
For CI/CD pipelines, OpenID Connect (OIDC) is a better approach than storing static secrets in repository settings. OIDC lets a CI job obtain a short-lived token directly from your cloud provider (AWS, GCP, Azure) without any permanent secret being stored anywhere that could be scraped.
Implement egress filtering in CI/CD
Even if a malicious script executes successfully, egress filtering can prevent it from sending stolen data to the attacker. Configure your CI runners to allow outbound connections only to known legitimate destinations: your package registry, Git provider, and cloud provider APIs. Block everything else by default.
A worm running in that environment can steal credentials but can't exfiltrate them, breaking the attack chain before any damage reaches the registry.
The broader shift
This attack represents a meaningful change in what supply chain attacks target. Previous variants went after your application's dependencies. This one targets your development environment directly, using your editor's own automation features against you. The configuration files that make editors convenient are the same files being weaponized here.
Defending against it requires treating project configuration directories as untrusted code, not just trusted settings. That's a habit change, but it's the right response to where these attacks are heading.