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 Happened?
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
Compromised versions show a hiddenbundle.js
executed through a postinstall hook.Sep 14–15 – Suspicious installs raise alarms
Developers notice odd behavior in@ctrl/tinycolor
. Daniel dos Santos Pereira flags the anomaly.-
Sep 16 AM – Exfiltration pattern becomes clear
Secrets leak through:- A GitHub repo named Shai-Hulud with a double-base64
data.json
. - A GitHub Actions workflow serializing
${{ toJSON(secrets) }}
to a webhook.
- A GitHub repo named Shai-Hulud with a double-base64
Sep 16 PM – Propagation confirmed at registry scale
With stolen npm tokens, the worm republishes itself across all packages owned by compromised maintainers. Infection jumps from dozens to hundreds.Sep 16–17 and ongoing – Widespread impact
Infected npm packages continue to appear. Organizations freeze updates, enforce lockfile-only installs, and rotate all 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:
- Steal credentials at scale from developer laptops and CI runners (npm tokens, GitHub tokens, cloud creds).
- Propagate automatically by abusing the publishing rights of compromised maintainers.
- Persist and exfiltrate reliably through GitHub repos and workflows.
Likely payoff: long-lived access to registries and code, fast lateral movement into cloud accounts, and further supply chain weaponization.
3.2 Inside the Shai-Hulud Payload: bundle.js
- Large Webpack-bundled and heavily minified JavaScript file (~3–3.7 MB).
- Executes from a postinstall hook in
package.json
.
Traits:
- Minified module graph with numeric IDs.
- Base64 string hiding.
- Dynamic eval-style dispatch.
- OS filtering to prefer Linux/macOS.
3.3 Install-Time Execution
Discovery and harvesting:
- Dumps
process.env
, scans for secrets, runs TruffleHog. - Queries cloud metadata endpoints (AWS:
169.254.169.254
, GCP:metadata.google.internal
).
Exfiltration:
- Creates public repo Shai-Hulud with double-base64
data.json
. - Plants a GitHub Actions workflow serializing
${{ toJSON(secrets) }}
.
Propagation:
- Injects
bundle.js
+ postinstall into packages owned by compromised maintainers, republishes malicious versions.
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 toJSON(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. Any secret found becomes immediately useful.
- Exfiltration occurs in two ways:
- By creating a public repo named Shai-Hulud with a double base64-encoded
data.json
. - By planting a GitHub Actions workflow that posts
${{ toJSON(secrets) }}
to a webhook.
- By creating a public repo named Shai-Hulud with a double base64-encoded
- Using any stolen npm token, the worm republishes all other packages owned by the compromised maintainer with the same malicious hook.
- 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
Scan npm packages and tarballs before they reach developers or CI jobs. Oversized bundle.js
files, suspicious postinstall hooks, and obfuscation markers are early red flags. Enforcing cool-off periods and pinned versions in pipelines also 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. They also block workflows that serialize secrets or attempt external posts. Teams should 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. 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 appearing without reason, or fresh public repos filled with strange encoded files can all signal worm activity. Teams should raise alerts quickly and isolate any maintainers or runners showing these 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.
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
SHA-256 digests matching observed bundle.js
samples:
-
46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09
-
81d2a004a1bca6ef87a1caf7d0e0b355ad1764238e40ff6d1b1cb77ad4f595c3
-
dc67467a39b70d1cd4c1f7f7a459b35058163592f4a9e8fb4dffcbba98ef210c
Other patterns:
- A
bundle.js
at the package root. -
"postinstall": "node bundle.js"
insidepackage.json
. - Repositories named Shai-Hulud.
- GitHub workflows containing
${{ toJSON(secrets) }}
.
Behavioral IoCs
Beyond file signatures, worm activity shows itself through behavior:
- 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
bash
# 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 republished itself automatically. Because of this, the attack spread in hours instead of weeks.
Lessons for Developers and DevOps Teams:
- 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, one compromise can quickly hit production.
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 are now the foundation of resilience in modern pipelines.
At Xygeni, we see Shai-Hulud 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.
Analysts observed this loop at scale, which explains the rapid jump from dozens to hundreds of infected packages.
For the full list of compromised packages, please visit the blog: Shai-Hulud: The npm Packages Worm Explained
Top comments (0)