A massive, active supply chain attack has struck the Node.js ecosystem. On August 4, 2026, the maintainer account behind foundational utilities like keyv, cacheable, flat-cache, and file-entry-cache was compromised.
Attackers pushed trojanized releases containing a self-propagating, credential-stealing worm an evolved version of the Shai-Hulud malware family. The infected packages collectively account for over 2 billion monthly installs, deep within the dependency trees of millions of JavaScript and TypeScript projects worldwide (e.g., transitively required by tooling like ESLint).
Here is a full breakdown of what happened, how the malware operates, and the critical steps you must take right now.
Executive Summary
Scope: 868+ npm packages (and growing) carrying over 2 billion monthly downloads.
Initial Compromise: GitHub / npm maintainer account hijacked to push malicious commits and publish signed tarballs.
Infection Mechanism: A preinstall hook (node setup.mjs) silently downloads a standalone Bun runtime to execute an obfuscated second-stage payload (Math_Symbol.js / math_init.js).
Worm Behavior: Uses harvested npm tokens to unpack maintainer tarballs, inject the malicious lifecycle scripts, bump patch versions, and republish them back to npm automatically complete with signed Sigstore provenance.
Exfiltration Strategy: Stolen credentials are encrypted and pushed to attacker-created public GitHub repositories with the description Shai-Hulud: Here We Go Again, or exfiltrated via C2 domains fetched dynamically from an Ethereum smart contract.
The "Dead-Man's Switch" Trap: DO NOT ROTATE YOUR CREDENTIALS YET. The malware installs a background system service that monitors your GitHub token for revocation. If it detects a 4xx error, it triggers a payload execution!
How the Attack Works (Technical Breakdown)
The published library code inside dist/ is identical to clean releases. The compromise lives entirely within the npm package lifecycle hooks.
{
"files": [
"dist",
"LICENSE",
"setup.mjs",
"Math_Symbol.js"
],
"scripts": {
"preinstall": "node setup.mjs"
}
}
Stage 1: The Bun Loader (setup.mjs)
When npm install runs, setup.mjs executes automatically. It inspects the host system, downloads a standalone Bun runtime v1.3.13 binary directly from GitHub releases, unzips it into temporary directories (/tmp/bun-dl-*), and uses Bun to run Stage 2.
Using Bun allows the payload to execute rich TypeScript/JavaScript tooling while bypassing endpoint controls that monitor standard node process trees.
Stage 2: The Payload (Math_Symbol.js)
The second stage is a ~728 KB bundle with strings protected by polymorphic basE91 encoding. Upon execution, it triggers three main actions:
1. Broad-Spectrum Credential Harvesting
The payload scans disk and memory for virtually every major developer and infrastructure credential:
- Cloud & Meta Data: AWS IMDS (169.254.169.254), GCP service account keys, Azure credentials, Alibaba, Tencent Cloud CLI configs.
- Secrets & Orchestration: HashiCorp Vault tokens, Kubernetes service account tokens (/var/run/secrets/kubernetes.io/serviceaccount/), AWS Secrets Manager.
- Developer & CI/CD Credentials: ~/.npmrc tokens, GitHub PATs/OAuth/OIDC tokens, SSH keys, .env files, Docker registry credentials, Kubernetes configs, and GitHub Actions runner process memory.
- AI Tooling & Apps: Credentials for Claude Code, OpenAI, Cursor, Gemini, Slack, and Stripe keys (sk_live_*).
2. Self-Replicating Worm & IDE Persistence
- npm Propagation: The worm queries registry.npmjs.org/-/whoami and lists every package the stolen token has publish rights to. It pulls down current tarballs, adds preinstall: node setup.mjs, bumps the patch version, requests Fulcio signing certificates via Sigstore, and publishes infected versions to npm.
- IDE & Repository Persistence: If a GitHub token (ghs_) is available, it commits stealthy autostart hooks to .claude/settings.json and .vscode/tasks.json across up to 50 active repository branches. Opening the repo in VS Code or starting a Claude Code session triggers the malware no npm install necessary.
3. Smart-Contract Backed Exfiltration
- Stolen credential bundles are encrypted locally with the attacker's public RSA key. The payload attempts two exfiltration paths:
- GitHub Repositories: Automatically creates public repos with the description Shai-Hulud: Here We Go Again via GitHub GraphQL API.
- Blockchain Fallback: Queries an Ethereum smart contract (0xE1f2395ee43e45A1556EC6438a88c31B83493103) via RPC (eth_call) to retrieve active C2 domains (such as npm-cache[.]com), allowing the attackers to bypass static domain blocks effortlessly.
WARNING: The Host-Level "Dead-Man's Switch"
- Security teams routinely respond to compromised tokens by revoking them immediately. Doing so without clearing the host system first plays directly into the attacker's hands.
- The payload installs a background daemon (com.user.gh-token-monitor via macOS LaunchAgent or a Linux systemd user unit with enable-linger).
- A watcher script (~/.local/bin/gh-token-monitor.sh) polls the GitHub API with your token every 60 seconds. If the token returns an HTTP 4xx error (because you revoked it), the watcher evaluates a remote-supplied shell command stored on disk before self-destructing.
if [[ "$HTTP_STATUS" =~ ^40[0-9]$ ]]; then
eval "$HANDLER" # Triggered immediately upon token revocation!
rm -f "$STARTED_FILE"; exit 0
fi
Remediation Checklist for Developers & Teams
If you or your CI/CD environment ran npm install on an affected package version, follow this sequence strictly:
Step 1: Disarm the Dead-Man's Switch First
Do not revoke tokens yet. Inspect and delete the following persistence artifacts:
Files & Directories:
- ~/.local/bin/gh-token-monitor.sh
- ~/.config/gh-token-monitor/
- /tmp/bun-dl-*
- /tmp/gh-token-monitor.*
Daemons & Services:
- macOS: Unload and remove ~/Library/LaunchAgents/com.user.gh- token-monitor.plist
- Linux: Run systemd --user stop gh-token-monitor.service and loginctl disable-linger
Repository Configs:
- Clean .claude/settings.json and .vscode/tasks.json in local and remote git branches.
Step 2: Rotate Credentials
Once the system is disarmed and processes terminated, immediately rotate/revoke:
- npm auth tokens and OIDC trust relationships.
- GitHub PATs, SSH keys, and OAuth grants.
- AWS / GCP / Azure credentials and IAM roles.
- Vault tokens, Kubernetes service account tokens, and database connection strings.
Step 3: Pin Safe Dependency Versions
Lock affected packages to their clean pre-compromise releases (e.g.,
SOCIAL SHARE CARD GENERATOR