Back to Scaling Node.js applications guides

The Shai-Hulud Worm: A New Cross-Ecosystem Supply Chain Attack

Stanley Ulili
Updated on July 13, 2026

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 diagram illustrating the four-stage replication cycle of the Shai-Hulud worm: Install, Steal, Republish, and Spread.

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.

A simple animation showing the worm jumping from the NPM packages ecosystem to the Go (Verana Blockchain) ecosystem.

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 on folderOpen
  • .claude/settings.json, configured to execute on SessionStart

Both trigger a command like node .claude/setup.mjs automatically when the project is opened.

A code display showing the malicious file structure, highlighting the `.claude/` and `.vscode/` directories and the JSON and MJS files within them.

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.

A text box from the security report highlighting that the new malware persistence mechanism survives an `npm uninstall` operation.

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:

 
ls -la

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.

The VS Code command palette is open, showing the "Tasks: Manage Automatic Tasks" command being selected, leading to an option to "Disallow Automatic Tasks".

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 network diagram showing a CI runner with allowed outbound connections to legitimate services (NPM, Git, Cloud APIs) and a blocked, dashed-line connection to an "attacker C2" server.

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.

Got an article suggestion? Let us know
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.