# ChainDrop: Massive npm Supply-Chain Infostealer Worm Compromises 1,300+ Packages via Keyv Maintainer Account Hijack

> A self-propagating infostealer worm dubbed ChainDrop — based on the Shai-Hulud malware framework — has compromised over 1,300 npm packages (1,381 versions) by hijacking the GitHub account of the Keyv/cacheable ecosystem maintainer. The worm embeds a Bun runtime dropper via a preinstall script that deploys a multi-target credential stealer against CI/CD runners, developer workstations, cloud platforms (AWS, GCP, Azure), HashiCorp Vault, Kubernetes secrets, and AI-agent credential stores. Exfiltration uses RSA+AES-256-GCM-encrypted envelopes sent to a public GitHub staging repo and the dead-drop domain npm-cache.com, whose address is resolved dynamically from an Ethereum smart contract. Every malicious package carries valid npm OIDC provenance signed by the legitimate GitHub Actions workflow — a critical bypass rendering provenance-based supply chain verification ineffective.

- **Published:** 2026-08-04T00:00:00Z
- **Last reviewed:** 2026-08-09T17:51:24.114Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-1872
- **ID:** TL-2026-1872
- **Severity:** CRITICAL
- **Category:** SUPPLY_CHAIN
- **Status:** ACTIVE
- **Actor:** Shai-Hulud Campaign
- **Detections:** 9 · **IOCs:** 43 (full data via the Threadlinqs MCP server — Purple tier)

## Description

On August 4, 2026 at approximately 09:02 UTC, an unidentified threat actor compromised the GitHub account of Jared Wray, the maintainer of the Keyv caching library (~153.7M weekly downloads) and the broader cacheable ecosystem. Over the next ~4 hours, the attacker pushed poisoned commits directly to main branches across multiple repositories, bypassing branch protection via the compromised account's credentials. The first malicious package published was keyv@6.0.0, which carried a preinstall script (setup.mjs) and an obfuscated 728 KB payload (Math_Symbol.js). Crucially, because the attacker pushed the poisoned release commit through the maintainer's own GitHub Actions workflows, npm's OIDC trusted publishing system and SLSA provenance attestation both registered as fully valid — the attestation faithfully recorded the poisoned commit hash. This demonstrates a critical gap: provenance proves which commit was built but cannot prove the commit was authorized.

The dropper (setup.mjs) is a cross-platform runtime downloader that fetches the legitimate Bun v1.3.13 runtime from the official oven-sh/bun GitHub releases — a living-off-the-land approach. It detects the platform and architecture (Linux x64 glibc/musl, Linux arm64, macOS x64/arm64, Windows x64/arm64), downloads the appropriate Bun artifact, stages it in /tmp/bun-dl-*, and executes Math_Symbol.js via Bun. After execution, it deletes the staging directory. A second variant of setup.mjs (11,017 bytes, SHA-256 fd3ca400) appeared in community-propagated packages, featuring a hand-rolled minimal ZIP extractor as fallback.

The Stage 2 payload Math_Symbol.js (or math_init.js, identical SHA-1 35a672cf) is a Bun-bundled CJS program weighing 727,680 bytes with three layers of obfuscation: a rotated 1,283-entry string table with custom charset decoder, anti-tamper Object.hardening, and AES-256-GCM encrypted configuration blobs. On execution, it reads GITHUB_REF and GITHUB_REPOSITORY for encrypted target matching on special-cased repos, checks for a Russian locale kill switch (LANG set to Russian causes the payload to exit with 'Exiting as russian language detected!'), detaches and self-respawns outside GitHub Actions via spawn(process.execPath) with _NODE_RUNTIME_INIT to survive the parent npm install process, and writes a camouflage lock file at /tmp/tmp.dpkg_<pid>.lock disguised as a dpkg lock.

The payload performs the most extensive credential harvesting observed in a supply-chain worm, targeting: GitHub tokens (PATs ghp_, OAuth gho_, runner ghs_ and JWT OIDC tokens), npm tokens (npm_ format validated against registry.npmjs.org/-/whoami), AWS credentials (env, ~/.aws/credentials, IMDSv2, ECS metadata, Secrets Manager via ListSecrets + GetSecretValue across 16 regions, SSM GetParameters with decryption), GCP credentials (~/.config/gcloud, service account JSON), Azure credentials (~/.azure/accessTokens.json), Kubernetes secrets (in-cluster API enumeration across all namespaces), HashiCorp Vault (token from env or file, Kubernetes auth, AWS IAM auth, mount enumeration + KV v1/v2 path walk), and over 140 filesystem hotspot paths including .npmrc, .git-credentials, SSH keys, Docker config, .env files, shell histories, password-store entries, and AI-agent credential stores (Claude, Codex, Cursor, OpenAI, Anthropic, Gemini, OpenClaw, OpenCode, Hermes, Kiro). Notably, the payload includes a sudo python3 helper that reads the GitHub Actions Runner.Worker process memory via /proc/*/mem to extract all secrets with 'isSecret':true in-scope from active workflow runs.

Self-propagation is the worm's defining feature. Using stolen npm tokens, the worm lists all publishable packages for the victim account via npm API queries, downloads each current tarball, bumps the patch version, injects setup.mjs + math_init.js, adds the preinstall hook, and republishes with self-generated Sigstore + SLSA provenance bundles. Using stolen GitHub ghs_ tokens, it pushes malicious .claude/settings.json and .vscode/tasks.json to any branch across up to 50 branches per repo — ensuring the payload executes when the repo is opened in VS Code or Claude Code without any npm install step. A separate 'Run Copilot' workflow commits a secrets-laundering workflow that writes ${{ toJSON(secrets) }} to format-results.txt and uploads it as an artifact, which the worm then downloads. A token-monitor service (gh-token-monitor.sh) is installed as either a systemd user timer or macOS LaunchAgent that polls api.github.com/user every 60s for 24h and fires an attacker-controlled handler when the token is revoked — effectively punishing credential rotation.

Exfiltration is dual-path. Primary path: AES-256-GCM-encrypted JSON envelopes wrapping gzip-compressed loot, with the AES key itself wrapped with an embedded RSA-4096 public key (RSA-OAEP-SHA256), sent via HTTPS POST to npm-cache.com:443/router. The C2 domain list is resolved dynamically from an Ethereum smart contract (0xE1f2395ee43e45A1556EC6438a88c31B83493103, selector 0x53ed5143) via eth_call against 75 public RPC endpoints, enabling the attacker to rotate infrastructure in real-time. Secondary path: The worm creates public GitHub repos with auto_init, commits harvested data as results-*.json files with the description 'Shai-Hulud: Here We Go Again'. At the time of publication, over 800 such staging repos existed. The C2 response is bidirectional: if it contains a 'code' field, the worm eval()s it — providing live remote-code-execution capability.

The attack propagated from the original 11 packages in the jaredwray/cacheable ecosystem to affect over 868 distinct packages (1,381 versions) across organizations including ServiceTitan (141 packages), OneReach (78), Or-SDK (74), Ornikar (42), Qlik (28), Nebula.js (22), UmaCloud (8), Arv-Bedrock (5), Deliveroo (2), Picsart (2), and others. Combined monthly downloads exceed 2 billion, placing this among the largest supply-chain attacks in history by ecosystem blast radius. The campaign is an evolution of the previously documented Shai-Hulud 2.0 ('The Second Coming', November 2025) with new capabilities including EtherHiding blockchain-based C2, RSA+AES analyst-proof exfiltration envelopes, AI-agent credential targeting, IDE persistence via Claude and VS Code configurations, and a Russian-language kill switch suggesting Russian-speaking operators.

## MITRE ATT&CK

- T1195 Supply Chain Compromise
- T1195.002 Compromise Software Supply Chain
- T1195.001 Compromise Software Dependencies and Development Tools
- T1204.002 Malicious File
- T1059.007 JavaScript
- T1059.006 Python
- T1202 Indirect Command Execution
- T1543.001 Launch Agent
- T1053.005 Scheduled Task
- T1601 Modify System Image
- T1036.005 Match Legitimate Resource Name or Location
- T1027 Obfuscated Files or Information
- T1070 Indicator Removal
- T1078 Valid Accounts
- T1528 Steal Application Access Token
- T1552.001 Credentials In Files
- T1552.004 Private Keys
- T1552.005 Cloud Instance Metadata API
- T1526 Cloud Service Discovery
- T1613 Container and Resource Discovery
- T1087 Account Discovery
- T1071.001 Web Protocols
- T1572 Protocol Tunneling
- T1567.002 Exfiltration to Cloud Storage
- T1560.003 Archive via Custom Method
- T1199 Trusted Relationship
- T1543.002 Create or Modify System Process
- T1005 Data from Local System
- T1102.002 Web Service
- T1041 Exfiltration Over C2 Channel
- T1583.001 Acquire Infrastructure
- T1588.002 Obtain Capabilities

## Sources

- [Massive ChainDrop npm supply-chain attack infects hundreds of packages](https://www.bleepingcomputer.com/news/security/massive-chaindrop-npm-supply-chain-attack-infects-hundreds-of-packages/)
- [ChainDrop npm Worm: Bun-loaded CI/CD credential harvester with Ethereum dead-drop C2](https://www.stepsecurity.io/blog/chaindrop-npm-worm)
- [Major Shai Hulud campaign strikes npm again](https://research.jfrog.com/post/shai-hulud-is-back-august/)
- [Keyv and Cacheable npm Supply Chain Attack](https://wiz.io/blog/keyv-and-cacheable-npm-supply-chain-attack)
- [Keyv and Friends Compromised in npm Supply Chain Attack](https://aikido.dev/blog/keyv-and-friends-compromised-in-npm-supply-chain-attack)
- [A New Infostealer Worm Hits npm Affecting Keyv and Cacheable](https://ox.security/blog/a-new-infostealer-worm-hits-npm-affecting-keyv-and-cacheable)
- [Wiz Research IoCs - Keyv Supply Chain Attack](https://github.com/wiz-sec-public/wiz-research-iocs/blob/main/reports/keyv-packages.csv)

## Full data

Detection queries (Splunk SPL / Microsoft KQL / Sigma) and IOC values require the Threadlinqs MCP server (Purple tier): https://intel.threadlinqs.com/mcp

Canonical: https://intel.threadlinqs.com/threat/TL-2026-1872
