Shai hulud - npm packages - supply chain attack

Shai-Hulud: The npm Packages Worm Explained

TL;DR

On September 14, 2025, researchers identified Shai-Hulud, a self-replicating worm hidden inside npm packages, turning a routine dependency update into a full-scale supply chain attack. First spotted in the @ctrl/tinycolor package by Daniel dos Santos Pereira, Shai-Hulud harvests secrets, exfiltrates them through GitHub repos and workflows, and republishes itself across the registry using stolen credentials. Within days, the number of infected packages jumped from dozens to hundreds, confirming that Shaihulud is not just another trojan but a worm designed to spread automatically across the npm ecosystem.

Impact: any developer or CI runner installing public npm packages is at risk.

Immediate actions: block known versions, switch to lockfile-only installs, rotate npm and GitHub tokens, audit workflows, and monitor for Indicators of Compromise (IoCs).

What Happen?

The Shai-Hulud supply chain attack in npm packages is one of the most disruptive incidents in recent memory. Unlike isolated trojans, this worm mixes credential theft, automated exfiltration, and self-replication. Consequently, the infection timeline shrank from weeks to just hours.

For DevOps teams, the lesson is clear: if every install can execute code, then every dependency update is a potential breach point.

Possible Initial Vector and Targeted Credentials

Early analysis indicates that the attack likely started with stolen credentials. For instance, phishing campaigns spoofing npm login or MFA prompts may have captured developer tokens. Once attackers gained that first foothold, the worm spread by embedding itself into npm packages and stealing more secrets from:

  • npm config files like .npmrc, often containing publish tokens.
  • Environment variables and configs with GitHub PATs and CI/CD secrets.
  • Cloud metadata endpoints (AWS, GCP, Azure) yielding short-lived credentials for lateral movement.

Therefore, credential theft became the launchpad. With valid npm tokens and GitHub secrets, Shai-Hulud could self-replicate across multiple packages and repositories without extra human effort.

Timeline of the Shai-Hulud Supply Chain Attack in npm Packages

Sep 14, 2025 18:35 UTC

First infected releases land on npm

The first compromised versions appear in the registry. Early low-profile npm packages show a hidden bundle.js file that executes through a postinstall hook.

Sep 14–15

Suspicious installs raise alarms

Developers notice odd behavior in @ctrl/tinycolor. At this point, Daniel dos Santos Pereira publicly flags the anomaly, which triggers broader investigation.

Sep 16 AM

Exfiltration pattern becomes clear

Hosts begin dumping environment variables, running TruffleHog, and querying cloud metadata services. As a result, secrets leak through two channels: - A new GitHub repo called Shai-Hulud containing a double-base64 data.json. - A GitHub Actions workflow that serializes ${{ toJSON(secrets) }} and posts it to a static webhook.

Sep 16 PM

Propagation confirmed at registry scale

With valid npm tokens in hand, the worm republishes infected versions across all packages owned by compromised maintainers. Consequently, the infection jumps from dozens to hundreds in a few hours.

Sep 16–17 and ongoing

Widespread impact

The list of infected npm packages keeps growing. Since not all versions are removed at once, many organizations freeze updates, enforce lockfile-only installs, and rotate npm, GitHub, and cloud tokens.

The early vector shows how fragile modern pipelines can be. A single stolen npm token or GitHub secret opened the door for the Shai-Hulud supply chain attack in npm packages. Once inside, the worm scaled fast, turning one compromised maintainer into hundreds of infected projects. Therefore, the real risk is not only technical but also organizational: what happens in hours can ripple through CI/CD systems, developer laptops, and cloud accounts.

2. Executive Impact of the Shai-Hulud Supply Chain Attack

Shai-Hulud is still active today. This worm speeds up the impact timeline. What once took weeks with a trojan now unfolds in hours. As a result, the spread is faster and harder to contain. It advances by:

  • Stealing npm publish tokens and GitHub secrets.
  • Republishing itself into other npm packages.
  • Adding malicious GitHub Actions workflows for persistence.

Who is affected:

Any team that installs public npm packages is exposed. Moreover, developers with cached npm or GitHub tokens face high risk. CI runners that use broad-scoped secrets are also vulnerable.

Business risk

The business impact grows quickly. Stolen tokens can lead to account takeover, package hijacking, and even cloud misuse. In addition, persistence in GitHub workflows makes it harder to clean. Therefore, teams must treat Shai-Hulud as an ongoing incident, not a closed one.

3. How the Shai-Hulud Supply Chain Attack Works in npm Packages

3.1 Attacker objectives and motive

The campaign optimizes for three things:

  • The first objective is to steal credentials at scale from developer laptops and CI runners. This includes npm publish tokens, GitHub tokens, and cloud credentials. In fact, multiple analyses confirm systematic secret harvesting, such as running TruffleHog and querying cloud metadata endpoints.
  • The second objective is to propagate automatically by abusing the publishing rights of compromised maintainers. As a result, one foothold quickly expands into many, since new infected versions of other packages appear without additional human effort.
  • The third objective is to persist and exfiltrate reliably through GitHub infrastructure. Attackers create a public repo named “Shai-Hulud” with a double base64 data.json, and in addition they plant a workflow that serializes ${{ toJSON(secrets) }} and posts it to a static webhook.

The likely payoff includes long-lived access to registries and source code, fast lateral movement into cloud accounts, and the option to weaponize the supply chain further. Public reporting shows private repos flipped to public with a “-migration” suffix, which increases data exposure and momentum

3.2 Inside the Shai-Hulud Payload: bundle.js in npm Packages

Shai-Hulud ships as a large, Webpack-bundled and heavily minified JavaScript file (bundle.js, about 3–3.7 MB) that executes from a postinstall hook in package.json. Consequently, every install triggers the payload automatically. This design hides identifiers, compresses control flow, and pushes all logic into a single artifact that executes during installation. Analysts consistently confirm Webpack bundling, unusual file size, and install-time execution.

Obfuscation and anti-analysis traits you will see in samples:

  • Minified module graph with numeric module IDs, sparse comments, and flattened control flow. Moreover, this structure makes manual review extremely hard.
  • String hiding via base64 layers and construction helpers. For example, repeated base64 encoding and decoding usually appear around exfiltration routines.
  • Dynamic dispatch through eval-style patternsand generated function bodies, therefore enabling code to shift behavior at runtime.
  • OS filtering to prefer Linux and macOS execution particularly on CI runners and developer laptops.

From a functional angle, the bundle is modular. Writeups describe modules for OS discovery, filesystem and git secret scans, cloud SDK access, GitHub API operations, and a propagation engine that edits other packages the maintainer owns. In fact, StepSecurity and ReversingLabs both highlight a function that automatically updates packages with the malicious hook.

3.3 Install-Time Execution in Shai-Hulud: How npm Packages Trigger the Worm

The attack begins when postinstall runs node bundle.js. At this point, the script initializes and unpacks working state in memory, setting the stage for the worm’s full operation.

Discovery and harvesting

  • The payload dumps process.env and scans local files for high-entropy secrets and token prefixes. In addition, it extends coverage by running TruffleHog.
  • It queries cloud metadata endpoints to collect short-lived credentials. For instance, calls to 169.254.169.254 on AWS or metadata.google.internal on GCP often appear in infected hosts.
  • Consequently, any credential found becomes immediately usable to publish new npm packages or push GitHub workflows.

Exfiltration

  • The worm creates a new GitHub repo named Shai-Hulud and writes a double base64-encoded data.json with platform details, environment dumps, and secrets. As can be seen, this noisy behavior is easy to spot if defenders know where to look.
  • It also plants a GitHub Actions workflow, often on a branch named shai-hulud, that serializes ${{ toJSON(secrets) }} and posts the data to a static webhook. Furthermore, this workflow persists until someone actively removes it.

Propagation

  • With any discovered npm token, the payload enumerates all packages owned by the compromised maintainer. Then, it fetches each tarball, injects bundle.js and a postinstall entry, and republishes the package.
  • As a result, dozens of infected packages can appear in a matter of hours, multiplying the blast radius across the ecosystem.

Persistence and exposure

  • The worm keeps malicious workflows alive and, in several cases, flips private repos to public with a “-migration” suffix. Altogether, this ensures the attacker maintains a foothold and maximizes data leakage.

Key detection note
This unusual use of ${{ toJSON(secrets) }} in Actions workflows is rare. Therefore, teams should treat it as a high-signal indicator during hunts.

Sanitized workflow pattern you should hunt for

This unusual use of to JSON(secrets) in Actions is a high-signal indicator in this incident.

High-level propagation pseudo-code (safe, descriptive)

async function propagate(token, owner) {
  const pkgs = await npmApi.listPackages(owner, token);
  for (const p of pkgs) {
    const tgz = await npmApi.fetchTarball(p, token);
    const modified = injectBundleAndPostinstall(tgz); // adds bundle.js + "postinstall"
    await npmApi.publish(modified, token);            // publishes new malicious version
  }
}

Analysts observed this loop at scale, which explains the rapid jump from dozens to hundreds of infected packages.

3.4 Why this is a worm in a package ecosystem

A worm is malware that spreads on its own without requiring manual operator steps at every stage. In operating systems, worms usually exploit network vulnerabilities to move from one machine to another. In contrast, Shai-Hulud operates inside the npm registry. Its efficient path is through credential reuse.

The worm takes advantage of stolen npm publish tokens. As soon as it obtains valid credentials, it republishes infected versions under other packages owned by the same maintainer. Subsequently, those packages are installed by unsuspecting developers or CI runners, and the cycle repeats.

For this reason, security analysts, including Dark Reading, classify Shai-Hulud as a self-replicating worm rather than a simple trojan or a typosquatting incident. The difference is important: a trojan typically compromises one host, but a worm amplifies its impact automatically across an ecosystem.

3.5 “How it works” cheat-sheet

To summarize Shai-Hulud’s lifecycle, here is a concise breakdown of its main steps:

  • A package with postinstall is installed, and bundle.js executes.
  • The payload dumps environment variables, scans files and git history, runs TruffleHog, and queries cloud metadata services. Consequently, any secret found becomes immediately useful.
  • Exfiltration occurs in two ways: first, by creating a public repo named Shai-Hulud with a double base64-encoded data.json; second, by planting a GitHub Actions workflow that posts ${{ toJSON(secrets) }} to a webhook.
  • Using any stolen npm token, the worm republishes all other packages owned by the compromised maintainer with the same malicious hook. In this way, the infection multiplies rapidly.
  • Finally, the attacker holds more secrets, more packages to spread through, and persistence inside GitHub accounts and repositories.

4. How to avoid this class of attack, practically

Shai-Hulud is a wake-up call. A worm that steals tokens and republishes itself is not a future risk, it is live in the npm packages ecosystem today. To prevent this type of supply chain attack, teams need controls that are programmable, automated, and enforced directly in CI/CD pipelines. These are the same defenses you can already implement with Xygeni.

Stop bad artifacts at the gate

You should scan npm packages and tarballs before they reach developers or CI jobs. Oversized bundle.js files, suspicious postinstall hooks, and obfuscation markers all serve as early red flags. Moreover, enforcing cool-off periods and pinned versions in pipelines prevents fresh, unvetted releases from being consumed automatically.

Harden CI/CD by default

Guardrails in CI/CD are essential. They reject merges or installs that introduce new scripts or binaries. At the same time, they block workflows that serialize secrets or attempt external posts. Teams should also require lockfile-only installs (npm ci) across all pipelines so dependency sets remain reproducible and safe.

Reduce the token blast radius

Secrets must not become single points of failure. Continuously scan code, configs, and pipeline output for exposed credentials. Tokens should be scoped narrowly, given short lifetimes, and rotated automatically when exposure is detected. As a rule, treat any token used on a host that executed a suspicious postinstall as compromised.

See worm behavior early

Anomaly detection is key. For example, sudden spikes in npm publish events, new workflows that appear without reason, or fresh public repos filled with strange encoded files can all signal worm activity. Therefore, teams should raise alerts quickly and isolate any maintainers or runners that show these warning signs.

Fix quickly without breaking builds

Speed and safety must go together. Automated pull requests can replace compromised npm packages with vetted versions. In addition, reachability and exploitability analysis ensures upgrades stay minimal and stable. Finally, rebuild affected CI runners from clean images once exposure is confirmed, keeping the attack from spreading further.

5. Indicators of Compromise (IoCs)

When analyzing Shai-Hulud, teams should watch for both static IoCs in files and behavioral IoCs in pipelines. Together, these signals help detect infections early and respond before the worm spreads further.

Static IoCs

The following SHA-256 digests match observed bundle.js samples:

  • 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09
  • 81d2a004a1bca6ef87a1caf7d0e0b355ad1764238e40ff6d1b1cb77ad4f595c3
  • dc67467a39b70d1cd4c1f7f7a459b35058163592f4a9e8fb4dffcbba98ef210c

Additionally, watch for these recurring patterns:

  • A bundle.js at the package root.
  • "postinstall": "node bundle.js" inside package.json.
  • Repositories named Shai-Hulud.
  • GitHub workflows containing ${{ toJSON(secrets) }}.

Behavioral IoCs

Beyond file signatures, worm activity reveals itself through behavior. For example:

  • Sudden bursts of npm publish events from one maintainer.
  • New workflows that push data to external endpoints.
  • Outbound POST requests triggered from CI runners.
  • Recently created public repos with encoded blobs.

Quick hunts

# Find postinstall in package.json
grep -R --line-number '"postinstall"' --include="package.json" /path/to/archives

# Detect tarballs with bundle.js
find /path/to/tarballs -name "*.tgz" -print0 \
 | xargs -0 -n1 -I{} sh -c 'tar -tf "{}" | grep bundle.js && echo "== {}"'

# Search workflows for toJSON(secrets)
grep -R --line-number "toJSON(secrets)" --include="*.yml" .github || true

6. Conclusion: Lessons from Shai-Hulud

The Shai-Hulud supply chain attack in npm packages shows how fragile today’s software supply chain has become. This worm did more than add malicious code. It stole tokens, sent data out, and then republished itself automatically. Because of this, the attack spread in hours instead of weeks.

For developers and DevOps teams, the lessons are clear:

  • Every install runs code. Even a common npm package can hide a postinstall worm.
  • Every token has high value. Once stolen, it can be used to spread malware further.
  • Every pipeline needs checks. Without guardrails on dependencies, workflows, and secrets, one compromise can quickly hit production.

Therefore, stopping attacks like Shai-Hulud requires controls that are automatic and easy to enforce. Teams should scan npm packages before installs, use lockfile builds, detect strange publishing activity, and keep tokens short-lived. These steps are no longer optional. Instead, they are the foundation of resilience in modern pipelines.

At Xygeni, we see the Shai-Hulud supply chain attack as a warning for the entire open source ecosystem. The sustainable way forward is to bring supply chain security directly into the development process, at the point where code, npm packages, and pipelines connect.

Below is the full list of npm packages and versions reported as compromised in Shai-Hulud. Use it to check your lockfiles, registries, and CI pipelines for exposure.

List of compromised packages

📦 Preview of Compromised npm Packages

Package Name Version Publish Date
json-rules-engine-simplified0.2.12025-09-14T17:58:51.203Z
airpilot0.8.82025-09-14T18:35:07.600Z
mcp-knowledge-graph1.2.12025-09-14T18:35:09.494Z
airchief0.3.12025-09-14T18:35:09.521Z
jumpgate0.0.22025-09-14T18:35:09.651Z
tvi-cli0.1.52025-09-14T18:35:10.996Z
@thangved/callback-window1.1.42025-09-14T20:31:38.479Z
@tnf-dev/api1.0.82025-09-14T20:31:39.547Z
@tnf-dev/js1.0.82025-09-14T20:31:41.251Z
@tnf-dev/mui1.0.82025-09-14T20:31:41.259Z
@tnf-dev/core1.0.82025-09-14T20:31:42.728Z
@teselagen/react-table6.10.202025-09-14T20:37:08.597Z
@hestjs/demo0.1.22025-09-14T20:45:52.348Z
@nexe/eslint-config0.1.12025-09-14T20:45:53.625Z
@hestjs/eslint-config0.1.22025-09-14T20:45:55.044Z
@nexe/config-manager0.1.12025-09-14T20:45:55.066Z
@nexe/logger0.1.32025-09-14T20:45:55.170Z
@hestjs/logger0.1.62025-09-14T20:45:55.197Z
@hestjs/validation0.1.62025-09-14T20:45:55.595Z
@hestjs/core0.2.12025-09-14T20:45:55.888Z
➡️ View Full List of Compromised Packages
Package Name Version Publish Date
json-rules-engine-simplified0.2.12025-09-14T17:58:51.203Z
airpilot0.8.82025-09-14T18:35:07.600Z
mcp-knowledge-graph1.2.12025-09-14T18:35:09.494Z
airchief0.3.12025-09-14T18:35:09.521Z
jumpgate0.0.22025-09-14T18:35:09.651Z
tvi-cli0.1.52025-09-14T18:35:10.996Z
@thangved/callback-window1.1.42025-09-14T20:31:38.479Z
@tnf-dev/api1.0.82025-09-14T20:31:39.547Z
@tnf-dev/js1.0.82025-09-14T20:31:41.251Z
@tnf-dev/mui1.0.82025-09-14T20:31:41.259Z
@tnf-dev/core1.0.82025-09-14T20:31:42.728Z
@teselagen/react-table6.10.202025-09-14T20:37:08.597Z
@hestjs/demo0.1.22025-09-14T20:45:52.348Z
@nexe/eslint-config0.1.12025-09-14T20:45:53.625Z
@hestjs/eslint-config0.1.22025-09-14T20:45:55.044Z
@nexe/config-manager0.1.12025-09-14T20:45:55.066Z
@nexe/logger0.1.32025-09-14T20:45:55.170Z
@hestjs/logger0.1.62025-09-14T20:45:55.197Z
@hestjs/validation0.1.62025-09-14T20:45:55.595Z
@hestjs/core0.2.12025-09-14T20:45:55.888Z
sca-tools-software-composition-analysis-tools
Prioritize, remediate, and secure your software risks
7-day free trial
No credit card required

Secure your Software Development and Delivery

with Xygeni Product Suite