No-BS security research

Supply Chain Persistence and Shai-Hulud

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.

FeatureVersion 1.0 (Sept 2025)Version 2.0 (Nov 2025)Version 3.0 (Jan 2025)
Primary VectorPost-install scriptsPre-install scriptsPre-install scripts (refined)
Payload Namebundle.jssetup_bun.jsbun_installer.js / environment.source.js
RuntimeNode.jsBun (Unix-focused)Bun (Windows & Unix support)
Destructive CapacityNoneDead man’s switchNone (dead man’s switch removed)
Repo Tagline“Shai-Hulud”“The Second Coming”“Goldox-T3chs: Only Happy Girl”
Target OSAgnosticLinux/macOS dominantWindows, Linux, macOS
ObfuscationLowModerateHigh (Manual Leetspeak)
Figure 1. High-level comparison of Shai-Hulud versions 1-3

Let’s take a closer look at how Shai-Hulud 3.0 works:

  1. The attack begins when a developer or CI/CD pipeline executes npm install on a project containing the compromised dependency. The package.json file contains a defined preinstall hook, which generally has an easier job bypassing static analysis tools that scan the node_modules directory post-installation.
  2. 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.exe and handles Windows-specific path delimeters.
    • If Linux/macOS: The script proceeds with the standard bun command.
  3. Once the environment is primed, the loader executes the main payload, contained in the environment_source.js file.
    • 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 to 3nvir0nm3nt and cl0vd. These changes were likely made to break YARA rules and other static signatures developed after Shai-Hulud 2.0 was released.
  4. 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:

  1. Upon execution of the malware, it scrapes the user’s .npmrc file for authentication tokens.
  2. A query is run against the npm registry to list all packages maintained by the compromised user.
  3. The victim’s packages are downloaded and the malicious preinstall script and payload files are injected.
  4. 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 install sequence that spawns a child process executing bun or bun.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.com requesting files matching the pattern c0nt3nts.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”
  • The presence of pigS3cr3ts.json, 3nvir0nm3nt.json, or cl0vd.json in 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-scripts by 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