The commonality of supply chain-focused malware has seen remarkable growth in the past 24 months, with Shai-Hulud, aptly named after the giant sandworms in Frank Herbert’s Dune, taking much of the spotlight. First detected in September 2025, Shai-Hulud is a self-propagating worm that targets the npm JavaScript package registry. Now in its third iteration, Shai-Hulud has gone through a number of changes in strategy over Q4 of 2025 and now into Q1 of 2026.
How It Works
Shai-Hulud 2.0’s most definitive feature was a “dead man’s switch”, a failsafe designed to wipe user data if communication with C2 was lost. However, Shai-Hulud 3.0 has abandoned a number of these aggressive tactics in favor of sustained presence and credential harvesting.
| Feature | Version 1.0 (Sept 2025) | Version 2.0 (Nov 2025) | Version 3.0 (Jan 2025) |
| Primary Vector | Post-install scripts | Pre-install scripts | Pre-install scripts (refined) |
| Payload Name | bundle.js | setup_bun.js | bun_installer.js / environment.source.js |
| Runtime | Node.js | Bun (Unix-focused) | Bun (Windows & Unix support) |
| Destructive Capacity | None | Dead man’s switch | None (dead man’s switch removed) |
| Repo Tagline | “Shai-Hulud” | “The Second Coming” | “Goldox-T3chs: Only Happy Girl” |
| Target OS | Agnostic | Linux/macOS dominant | Windows, Linux, macOS |
| Obfuscation | Low | Moderate | High (Manual Leetspeak) |
Let’s take a closer look at how Shai-Hulud 3.0 works:
- The attack begins when a developer or CI/CD pipeline executes
npm installon a project containing the compromised dependency. Thepackage.jsonfile contains a definedpreinstallhook, which generally has an easier job bypassing static analysis tools that scan thenode_modulesdirectory post-installation. - The script triggers the execution of a file named
bun_installer.js, designed to mimic a legitimate setup utility. The primary function of this script is to ensure the presence of the Bun runtime.- If Windows: The script explicitly calls
bun.exeand handles Windows-specific path delimeters. - If Linux/macOS: The script proceeds with the standard
buncommand.
- If Windows: The script explicitly calls
- Once the environment is primed, the loader executes the main payload, contained in the
environment_source.jsfile.- Within the payload, a high degree of manual obfuscation can be found. For example, standard variable names related to secrets have been changed to terms like
pigS3cr3ts, and file names have been altered to3nvir0nm3ntandcl0vd. These changes were likely made to break YARA rules and other static signatures developed after Shai-Hulud 2.0 was released.
- Within the payload, a high degree of manual obfuscation can be found. For example, standard variable names related to secrets have been changed to terms like
- To begin harvesting credentials, the malware embeds or downloads a binary of TruffleHog, a legitimate open-source security tool. Discovered credentials are aggregated into .json files, which are then uploaded to attacker-controlled GitHub repositories. Some targeted credentials include:
- System environment variables
- Cloud credentials
- GitHub actions secrets
- TruffleHog scan results
- Repository source code & metadata
Propagation Mechanisms
In order to persist and propagate, the malware takes the following steps:
- Upon execution of the malware, it scrapes the user’s
.npmrcfile for authentication tokens. - A query is run against the npm registry to list all packages maintained by the compromised user.
- The victim’s packages are downloaded and the malicious
preinstallscript and payload files are injected. - The infected packages are republished to the registry.
Shai-Hulud can also dig into the victim’s GitHub presence, scanning for repositories and injecting malicious GitHub actions workflows.
However… Shai-Hulud v3.0 is currently hamstrung by a coding error. The malware saves local repository content to a file named c9nt3nts.json. When the propagation logic attempts to download credentials from previously infected peers, it requests a file named c0nt3nts.json (using the number 0 instead of the number 9). This triggers a 404 Not Found error, breaking the “fallback” infection chain. If the malware cannot find fresh credentials on the local machine, it cannot retrieve backup tokens from the botnet, effectively halting its spread on that specific node.
Prevention and Detection
Indicators of Compromise (IoCs) include:
- An
npm installsequence that spawns a child process executingbunorbun.exe. While Bun is a legitimate tool, its invocation inside an npn lifecycle script is highly suspicious. - Network signatures such as:
- Outbound traffic to
raw.githubusercontent.comrequesting files matching the patternc0nt3nts.json. - Traffic to
webhook.site(only used in some variants for initial beaconing) - API calls creating repositories with the description “Goldox-T3chs: Only Happy Girl”
- Outbound traffic to
- The presence of
pigS3cr3ts.json,3nvir0nm3nt.json, orcl0vd.jsonin the project root or temporary directories
The following logic can be applied to YARA rules for scanning node_modules and build artifacts:
rule ShaiHulud_v3_Goldox {
meta:
description = "Detects Shai-Hulud v3.0 'Goldox' variant artifacts"
author = "Threat Intelligence"
date = "2026-01-14"
severity = "Critical"
strings:
$json1 = "c9nt3nts.json"
$json2 = "3nvir0nm3nt.json"
$json3 = "pigS3cr3ts.json"
$desc = "Goldox-T3chs: Only Happy Girl"
$loader = "bun_installer.js"
$payload = "environment_source.js"
$marker = "SHA1HULUD"
condition:
any of ($json*) or $desc or ($loader and $payload) or $marker
}
For long term defense, consider taking the following actions:
- Configure CI/CD environments to run with
npm install --ignore-scriptsby default. - Mandate the utilization of lockfiles (
package-lock.json). - Transition to OpenID Connect (OICD) for CI/CD cloud access, eliminating long-lived static keys that can be harvested from disk.
Leave a comment