Skip to main content

Command Palette

Search for a command to run...

The Shai-Hulud Worm: Dissecting the Self-Spreading Malware Attack on the NPM Ecosystem

Updated
14 min read
The Shai-Hulud Worm: Dissecting the Self-Spreading Malware Attack on the NPM Ecosystem
H

Specialized in uncovering vulnerabilities within software supply chains and dependency ecosystems. Creator of SCAGoat and other open-source security tools. Speaker at Black Hat, DEF CON, and AppSec conferences with research on malicious package detection, dependency confusion, and CI/CD security.

1. Introduction: The Attack of the Code Worm

Imagine building a project with a set of Lego bricks, but one of the bricks is secretly malicious. Not only is it a bad piece, but it also has the ability to copy itself and sneak into all the other Lego sets you own. This is a simple way to understand the core of the Shai-Hulud malware attack.

In September 2025, a novel, self-spreading malware campaign named "Shai-Hulud" infected nearly 500 software packages in the popular npm registry, a massive library of code used by developers worldwide. This event was highly significant because it was the first observed malware to behave like a worm within the npm ecosystem, spreading itself automatically from one package to another without human intervention. To understand how this happened, we first need to look at the type of attack it was.

2. Understanding the Battlefield: What is a Software Supply Chain Attack?

A software supply chain attack is a cyberattack that targets a trusted, third-party component to compromise the final product. Think of it like a food supply chain: if a single, contaminated ingredient gets into the factory, it can spoil thousands of final food products that are shipped to stores. In the software world, modern applications are rarely built from scratch. Instead, developers assemble them using shared, open-source code "packages" from public registries like npm. If an attacker can poison one of these fundamental packages, the malware infects every single application that relies on it, spreading far and wide. The Shai-Hulud attack exploited this very principle to devastating effect.

3. Anatomy of the Attack: How Shai-Hulud Worked

The Shai-Hulud worm executed its attack in four primary stages, moving from initial compromise to automated self-propagation.

1. Initial Infection:

Getting a Foothold The attack began when threat actors compromised developers' accounts, likely using stolen credentials to inject malicious code into legitimate npm packages. Security researchers believe this attack was a follow-up to the earlier "s1ngularity/Nx" compromise, where a large number of developer tokens were initially stolen.

2. The Secret Hunt:

Weaponizing a Security Tool Once the malicious package was installed on a developer's machine, the malware activated a legitimate security tool called TruffleHog. Normally used to find accidentally exposed secrets, the attackers weaponized it to scan the victim's entire system for sensitive credentials and access tokens. The malware was specifically designed to hunt for:

NPM_TOKEN (keys to publish software packages)

GITHUB_TOKEN (keys to access code repositories)

AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (keys for Amazon Web Services)

◦ Credentials for Google Cloud and Azure

3. To further its reach, the malware also attempted "cloud metadata discovery," a sophisticated technique used to leak short-lived credentials from cloud build agents, which are often highly privileged.

4. Data Theft:

Exfiltrating the Loot The attackers employed several methods to steal and expose the harvested secrets, ranging from clumsy to catastrophic.

The Ineffective Webhook: The malware first attempted to send the stolen data to an endpoint on webhook.site, a service used for testing. However, the attackers used a free account that quickly hit its request limit and was deactivated, rendering this exfiltration method largely useless.

Public Exposure on GitHub: The malware's second, more alarming method involved using stolen GitHub tokens to expose data in two ways. First, it created new public repositories on a victim's account, named "Shai-Hulud," and uploaded the stolen secrets there for anyone to see. Even more destructively, it would find a victim's existing private repositories, make them public, and rename them with a -migration suffix, instantly leaking entire codebases and their embedded intellectual property.

5. The Worm Spreads:

Self-Propagation This was the most unique and dangerous part of the attack. If the malware found a valid npm token, it triggered its self-propagation mechanism by following a simple but effective four-step process:

1. It would download an existing package owned by the compromised developer from the npm registry.

2. It would "bump" the package version to the next incremental number (e.g., from 1.2.3 to 1.2.4).

3. It would copy its own malicious payload (bundle.js) into the package's code.

4. Finally, it would republish the newly infected version to npm, allowing the worm to spread to any developer who downloaded the update.

These actions combined credential theft with a mechanism for rapid, automated expansion, making the attack a landmark event in supply chain security.

the "Shai-Hulud" npm worm is a sophisticated, multi-stage piece of malware notable for being the first successful self-propagating attack in the npm ecosystem. Its primary functions are to steal credentials, exfiltrate data, and automatically spread itself to other npm packages.

The attack begins when a developer installs a compromised npm package. The package contains a malicious postinstall script that executes a large, minified JavaScript file named bundle.js. This script is a modular engine built with Webpack that orchestrates the attack.

Core Payload (bundle.js) Analysis

The bundle.js payload is a ~3.6MB file that functions as the malware's controller. It is designed to run on Linux or macOS systems, deliberately skipping Windows environments. The code is not obfuscated, which is a notable characteristic compared to other malware campaigns. The attack unfolds through several key modules.

1. Reconnaissance and Credential Harvesting

The worm performs comprehensive reconnaissance on the host system to gather secrets and credentials from multiple sources.

  • System Profiling: An OS Recon module gathers system information, including platform and architecture, and dumps all process environment variables (process.env). This captures any sensitive tokens stored in the environment, such as GITHUB_TOKEN, NPM_TOKEN, and AWS_ACCESS_KEY_ID.

  • Filesystem Scanning with TruffleHog: The malware downloads and executes a legitimate open-source tool called TruffleHog to scan the entire filesystem for high-entropy secrets, like AWS keys in ~/.aws/credentials. It weaponizes this tool to find and validate tokens.

  • Cloud Credential Theft: The payload includes specific modules to target cloud service providers:

    • AWS: It uses the AWS SDK to validate credentials and then enumerates and retrieves all secrets from AWS Secrets Manager across all regions. It specifically uses API calls like ListSecretsCommand and GetSecretValueCommand.

    • GCP: It uses the @google-cloud/secret-manager library to list and access secrets stored in Google Cloud Platform's Secret Manager.

    • Cloud Metadata Endpoints: The script probes cloud metadata endpoints for AWS (http://169[.]254[.]169[.]254) and GCP (http://metadata[.]google[.]internal) to find short-lived credentials available within cloud build agents.

2. Self-Propagation Mechanism

The most unique aspect of this malware is its ability to self-propagate, making it a true worm. This is achieved through its NpmModule.updatePackage function.

  1. Find NPM Token: The malware searches for a valid NPM_TOKEN in environment variables or the ~/.npmrc file and validates it using the /-/whoami endpoint.

  2. Enumerate Packages: If a valid token is found, it queries the npm registry to find up to 20 of the most popular packages the compromised maintainer has access to publish.

  3. Inject and Republish: The worm then downloads the tarball for each target package, unpacks it, and injects its own bundle.js payload. It also modifies the package.json file to add a postinstall script that executes node bundle.js.

  4. Propagate: Finally, it bumps the package patch version and republishes the newly trojanized version to the npm registry, continuing the infection cycle.

This process allows the attack to spread automatically and rapidly across the ecosystem without direct attacker intervention.

3. Data Exfiltration and Persistence

The worm uses multiple methods to exfiltrate the stolen data and establish persistence.

  • Webhook Exfiltration: The primary method is sending the aggregated JSON payload of stolen secrets to a hardcoded endpoint at webhook.site. This was somewhat neutralized because the attackers used a free account that was quickly deactivated due to excessive activity.

  • GitHub Repository Exfiltration: If a GITHUB_TOKEN is found, the malware creates a new public repository named "Shai-Hulud" in the victim's account and uploads the stolen data there. This serves as a secondary exfiltration channel.

  • GitHub Actions Backdoor: The worm injects a malicious GitHub Actions workflow file named shai-hulud-workflow.yml into the victim's repositories.

    • It creates a new branch named shai-hulud, commits the workflow file, and this workflow is triggered on any push event.

    • The workflow's sole purpose is to exfiltrate all of the repository's secrets (e.g., GITHUB_TOKEN, AWS keys) by sending ${{ toJSON(secrets) }} to the webhook endpoint. This ensures persistence beyond the initial host compromise.

  • Making Private Repositories Public: In a highly destructive action, the malware iterates through a user's private repositories, clones them, and republishes them as public repositories with a "-migration" suffix and "Shai-Hulud Migration" description. This is accomplished by a script dropped at /tmp/migrate-repos.sh.

Evolution of the Worm

The attacker released at least seven different versions of the worm, each with incremental improvements aimed at increasing efficiency and stealth.

  • V1 to V2: The second version added support for targeting Google Cloud (GCP) secrets instead of Azure, improved error handling to avoid exceptions when cloud credentials were not present, and added a debugging log line.

  • V2 to V3: This version improved stealth by removing the logging message, correcting race conditions, and making the GitHub token abuse more reliable by moving it earlier in the execution flow.

  • V3 to V4: The propagation was accelerated by increasing the number of packages the worm would attempt to infect in one pass from 10 to 20.

  • V4 to V5: To handle race conditions and reduce reconnaissance fingerprints in GitHub audit logs, the malware was changed to always create the "Shai-Hulud" repo without first checking if it existed.

  • V5 to V6: This version became stealthier by removing helper logs and adding a skip switch for the noisy filesystem scan. It also began exfiltrating the GitHub username.

  • V6 to V7: The final version removed a noisy Git manipulation technique, making the exfiltration process less likely to leave forensic artifacts on the host system.

4. Why Was This Attack a Big Deal?

The Shai-Hulud attack was a major escalation for several key reasons, setting it apart from previous supply chain incidents.

The First NPM Worm

Shai-Hulud's ability to self-propagate was a game-changer. Unlike previous attacks where attackers had to compromise each package individually, this malware could spread on its own once it found valid credentials. This automated capability made the attack faster, more widespread, and significantly harder to contain. The attackers didn't just release one version of the worm; over the course of the attack, they deployed at least seven iterations, with each new version making the malware faster, stealthier, and more effective at stealing data.

Widespread and Indiscriminate Impact

The attack compromised nearly 500 different packages, including those from major cybersecurity company CrowdStrike and the extremely popular @ctrl/tinycolor package, which has over 2.2 million weekly downloads and more than 8 million monthly downloads. Infecting such widely-used packages demonstrates the massive potential for downstream impact, affecting potentially millions of developers and applications.

Exposing Private Code and Intellectual Property

The malware’s methods for exposing data were uniquely damaging. It executed a dual threat against its victims. First, it stole and publicly posted sensitive credentials, such as API keys and access tokens, which could be used for follow-on attacks. Second, and far more catastrophically, it took victims' entire private source code repositories and made them public. This action could instantly expose a company's most valuable intellectual property, internal security secrets, and other sensitive data to the entire world, leading to catastrophic security and financial consequences.

5. How Can Developers Defend Against Such Attacks?

While no defense is perfect, there are several key practices that developers and organizations can adopt to significantly reduce their risk of falling victim to a supply chain attack like Shai-Hulud.

Protective MeasureWhy It Helps
Pin Dependency VersionsInstead of automatically accepting the latest updates to a software package, this practice forces a manual review. This prevents a compromised version from being automatically installed, acting as a crucial safety check.
Rotate Credentials RegularlyFrequently changing access keys and tokens (like passwords) limits the window of opportunity for an attacker. Even if a key is stolen, it will soon become useless.
Use Security Scanning ToolsEmploying tools that can check for known malware in packages before installation, or monitor for suspicious actions during installation, can help detect and block attacks before they do damage.
Ignore Run-Scripts During InstallationPrevents malware from executing automatically during the installation process (via a "postinstall" script) by running npm install --ignore-scripts. This can break some packages but is a powerful defense.
Delay Adoption of New VersionsTools like pnpm allow you to configure a minimumReleaseAge to avoid installing a package version until it has been available for a set time (e.g., 24 hours). This creates a buffer for the security community to discover and flag a malicious new version before you install it.

Adopting these defensive postures is crucial for building more resilient software in an increasingly hostile environment.

6. How to check if you’re infected?

Based on the sources provided, here are the commands and queries you can use to check for signs of compromise from the "Shai-Hulud" supply chain attack. The commands are grouped by the area you are investigating: your local filesystem, your project's dependencies, your GitHub environment, and your cloud infrastructure.

1. Checking Your Local Filesystem and Logs

These commands help you look for artifacts left by the malware on developer machines or build agents.

  • Search for the malicious bundle.js file by its SHA-256 hash. One of the most reliable indicators is the presence of the malicious script itself. You can find it using its known hash.

      find . -type f -name "*.js" -exec sha256sum {} \; | grep "46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09"
    
  • Check for temporary script files dropped by the malware. The worm was observed deploying scripts to the /tmp directory.

      ls /tmp/processor.sh
      ls /tmp/migrate-repos.sh
    
  • Review NPM logs for execution of the malicious script. The malware uses a postinstall script to run. You can check your npm logs to see if it was executed.

      grep "postinstall node bundle.js" ~/.npm/_logs/*
    
  • Search system and CI/CD logs for connections to the exfiltration endpoint. The malware sends stolen credentials to a specific webhook URL. Note that log file locations may vary depending on your system.

      grep "webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7" /var/log/*
    

2. Checking Your Project's NPM Dependencies

This helps determine if you have installed one of the compromised package versions.

  • Check if a specific vulnerable package is in your project's dependency tree. You can run npm ls for any of the known affected packages.

      npm ls @ctrl/tinycolor
      npm ls ngx-bootstrap
    

3. Checking Your GitHub Environment

The malware interacts heavily with GitHub, creating repositories, workflows, and branches.

  • Search your organization for the malicious workflow file. The malware creates a file named shai-hulud-workflow.yml. You can use GitHub's code search, replacing ACME with your organization's name.

      https://github.com/search?q=org%3AACME+path%3A**%2Fshai-hulud-workflow.yml&type=code
    
  • Search for the malicious shai-hulud branch. The malware creates this branch to commit the malicious workflow. This script uses the gh CLI to check all repos in your organization.

      # Replace YOUR_ORG_NAME with your GitHub organization name
      gh repo list YOUR_ORG_NAME --limit 1000 --json nameWithOwner --jq '.[].nameWithOwner' | while read repo; do
        gh api "repos/$repo/branches" --jq '.[] | select(.name == "shai-hulud") | "'$repo' has branch: " + .name'
      done
    
  • Search for publicly created "Shai-Hulud" repositories. The malware exfiltrates secrets to a public repository named "Shai-Hulud" within a compromised user's account. You can search for these, specifying your organization name.

    • Go to GitHub and search for: public repositories named Shai-Hulud in:<your_org_name>
  • Review your GitHub Security Log for suspicious activity. Look for unusual repository creation or renaming events.

4. Checking Cloud Infrastructure Logs (AWS & GCP)

The malware specifically targets cloud credentials and attempts to access secrets from AWS and GCP secret managers.

  • Check AWS CloudTrail for suspicious secret access. The malware enumerates secrets, so you should look for related API calls in your logs.

      aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=BatchGetSecretValue
      aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=ListSecrets
      aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue
    
  • Review GCP audit logs for unauthorized secret access. The malware uses the Secret Manager library to list and access secrets.

      # Review secret manager access logs
      gcloud logging read "resource.type=secretmanager.googleapis.com" --limit=50 --format=json
    
      # Check for unauthorized service account key creation
      gcloud logging read "protoPayload.methodName=google.iam.admin.v1.CreateServiceAccountKey"
    

Commands for Removal and Remediation

If you find evidence of a compromise, the sources recommend the following immediate actions:

  • Remove malicious packages and clean the cache.

      rm -rf node_modules && npm cache clean --force
    
  • Remove the malicious GitHub Actions workflow file.

      rm -f .github/workflows/shai-hulud-workflow.yml
    
  • Delete the malicious shai-hulud branch from your repositories.

      git push origin --delete shai-hulud
    

After running these checks, if you find any indicators of compromise, you should immediately rotate all potentially exposed credentials, including NPM tokens, GitHub tokens, cloud API keys, and any secrets stored in your environment.

7. Conclusion: A New Chapter in Supply Chain Threats

The Shai-Hulud attack was a landmark event, proving that self-propagating worms are not just a theoretical threat but a practical reality within the npm ecosystem. By combining credential theft, automated propagation, and the wholesale exposure of private intellectual property, the malware set a new and dangerous precedent for software supply chain attacks. This incident serves as a stark reminder of the interconnected nature of modern software and highlights the critical importance of vigilance, credential security, and defense-in-depth practices for every developer participating in the open-source world.

References:

https://socket.dev/blog/ongoing-supply-chain-attack-targets-crowdstrike-npm-packages

https://www.aikido.dev/blog/s1ngularity-nx-attackers-strike-again

https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised

https://socket.dev/blog/tinycolor-supply-chain-attack-affects-40-packages

https://www.getsafety.com/blog-posts/shai-hulud-npm-attack