DEV Community

Cover image for Shai-Hulud Strikes Back: Keyv, Cacheable & 800+ npm Packages Hijacked in Massive Worm Attack
Devam Chaudhari
Devam Chaudhari

Posted on

Shai-Hulud Strikes Back: Keyv, Cacheable & 800+ npm Packages Hijacked in Massive Worm Attack

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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., keyv@5.2.3, cacheable@1.8.8) or rely on clean lockfiles. Avoid loose semantic versioning ranges (^ or ~) that permit patch updates until upstream namespaces are remediated.

Package---Compromised Version
keyv --- 6.0.0
cacheable --- 2.5.1
flat-cache --- 6.1.24
file-entry-cache --- 11.1.6
cacheable-request --- 13.0.20
cache-manager --- 7.2.10
@cacheable/utils --- 2.5.1
@cacheable/memory --- 2.2.1


Indicators of Compromise (IOCs)

  • File Hashes (SHA-256):

    • setup.mjs (Loader): 54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668
    • Math_Symbol.js / math_init.js (Payload): 9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc
  • Domains & Infrastructure:

    • npm-cache[.]com
    • pypi-get[.]com
    • js-mirror[.]com
    • Ethereum Smart Contract: 0xE1f2395ee43e45A1556EC6438a88c31B83493103
    • User-Agent: Bun/1.3.13
  • GitHub Repository Marker: Public repos with the description string Shai-Hulud: Here We Go Again.

Sources:

Top comments (2)

Collapse
 
codearea_shop_1f1def9b532 profile image
Codearea

This is a serious reminder that even the most trusted dependencies can become attack vectors. If you're maintaining a codecan.net project, now is a good time to audit your package-lock.json or pnpm-lock.yaml, verify your dependency tree, and pin safe versions until the ecosystem is fully cleaned up. Supply chain security should be part of every release process.

Collapse
 
chaudharidevam profile image
Devam Chaudhari

Totally agree , the supply chain security is something we all need to take seriously. Auditing dependencies and locking safe versions should definitely be part of the release process. Appreciate you adding this to the discussion!