On August 4, 2026, a sophisticated software supply chain attack dubbed ChainDrop struck the npm ecosystem through the compromise of a maintainer's GitHub account for the widely used Keyv and Cacheable open-source packages. The resulting self-propagating worm spread to over 2251 versions of 452 unique packages with approximately 2 billion monthly downloads, affecting organisations including Deliveroo, Ornikar, OneReach, Picsart, Qlik, and ServiceTitan.
Keyv is a simple key-value store for Node.js with support for multiple backends, while Cacheable is its underlying HTTP cache framework. Together, these packages serve as foundational caching infrastructure across the JavaScript ecosystem. Both packages are maintained by the same developer, whose GitHub account was compromised by the threat actor.
The attacker published the first malicious release, keyv@6.0.0, at approximately 9:00 UTC on August 4, 2026. The malware is a descendant of the "Mini" Shai-Hulud family, attribution to TeamPCP remains unconfirmed. The worm propagated rapidly by harvesting npm and GitHub tokens from compromised environments and using them to publish further poisoned releases. Socket verified 2251 poisoned versions across 452 packages.
The payload steals a comprehensive range of sensitive data including cloud credentials, CI/CD secrets, developer tokens, AI configuration files, and cryptocurrency wallets. It also plants Claude Code and Visual Studio Code hooks for persistent IDE-based execution. No CVE identifiers have been assigned to this campaign at the time of writing.
Initial Access
The threat actor compromised the GitHub account of the Keyv maintainer, granting direct push access to the source repositories for Keyv, Cacheable, cache-manager, flat-cache, file-entry-cache, and numerous related packages within the maintainer's monorepo.
Malicious Publication
Starting at 9:00 UTC, the attacker introduced IDE persistence payloads to the Keyv repository, then published keyv@6.0.0 and subsequent malicious versions. Because the packages were built and published through their legitimate GitHub Actions workflows, the compromised releases carried valid SLSA provenance information, bypassing integrity verification controls. Each poisoned package contained a "preinstall": "node setup.mjs" entry in its package.json, causing the dropper to execute automatically before any npm install completed.
Worm Propagation
The malware's self-spreading capability allowed it to infect packages belonging to other maintainers whose projects depended on a previously compromised package. By harvesting valid npm publishing tokens from CI/CD runners and developer workstations, the worm republished downstream packages with the malicious payload injected, creating a cascading chain of compromise. This mechanism enabled the attack to escape the original Keyv/Cacheable namespace and propagate across organisational boundaries.
Affected Packages
The core compromised packages included:
|
Package Name |
Malicious Affected Version(s) |
Verified Safe / Restored Clean Version |
|
keyv |
6.0.0 (and subsequent malicious iterations) |
5.6.0 |
|
flat-cache |
6.1.24 |
6.1.23 |
|
file-entry-cache |
11.1.6 |
11.1.5 (or previous stable version) |
|
cacheable-request |
13.0.20 |
13.0.19 (or previous stable version) |
|
cache-manager |
7.2.10 |
7.2.9 |
|
cacheable |
2.5.1 |
2.5.0 (or previous stable version) |
|
@cacheable/utils |
2.5.1 |
2.5.0 (or previous stable version) |
|
@cacheable/memory |
2.2.1 |
2.2.0 (or previous stable version) |
|
@cacheable/node-cache |
3.1.2 |
3.1.1 (or previous stable version) |
|
@cacheable/net |
2.1.1 |
2.1.0 (or previous stable version) |
|
ecto |
5.0.1 |
5.0.0 (or previous stable version) |
The worm subsequently spread to packages within organisations such as Ornikar (including @ornikar/eslint-config-*, @ornikar/prettier-config, @ornikar/babel-preset-*, and many more), Qlik (@nebula.js/nucleus), HubSync (@hubsync/web-sdk-react), and numerous others. A full, continuously updated list is maintained by Wiz Research on their public GitHub repository.
Important: The npm registry changed rapidly throughout the incident. At 5:40 p.m. IST on August 4, earlier clean versions were restored as latest for at least nine core packages, including keyv@5.6.0, flat-cache@6.1.23, and cache-manager@7.2.9. Organisations must verify exposure using exact package versions resolved in lockfiles, not current registry tags.
Malware Analysis
Payload Overview
The malware deploys in a two-stage architecture:
Stage 1 - setup.mjs (Dropper): This lightweight Node.js script serves as the initial payload. It checks for the Bun JavaScript runtime and, if absent, downloads Bun v1.3.13 from the official GitHub releases. It then hands execution off to the main infostealer bundle before deleting the temporary runtime directory to reduce forensic footprint.
Stage 2 - Math_Symbol.js / math_init.js (Infostealer): A heavily obfuscated, 727,680-byte compiled bundle executed via the Bun runtime. This is the core credential-harvesting engine. Both filenames (Math_Symbol.js and math_init.js) have been observed in the wild, containing identical functionality at the same SHA-1 hash.
Credential Harvesting Targets
The infostealer systematically enumerates and exfiltrates:
Persistence Mechanisms
The Keyv repository retained separate Claude Code (.claude/settings.json) and Visual Studio Code (.vscode/) hooks. These IDE-level persistence mechanisms can execute the payload once a user trusts the workspace or permits the project configuration, providing a secondary execution path independent of npm install lifecycle scripts.
The malware also installs a credential-revocation watcher — a trap that triggers an attacker-supplied local handler when tokens are rotated. Responders must remove this watcher before rotating any credentials.
Command and Control
The malware retrieves its C2 domains from an Ethereum smart contract (StringListStore) using an eth_call rather than embedding them in the payload. This blockchain-based C2 resolution allows the operator to update infrastructure without modifying the malware. On-chain history shows the contract was initially configured with three domains before being updated to return only npm-cache[.]com. The contract owner was funded by an address previously flagged for scam activity.
Exfiltration
Stolen data is encrypted using a hybrid AES-256 and RSA-4096 scheme, then exfiltrated through two channels:
The initial commits to exfiltration repositories contain the intimidation string: "IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients."
Known Indicators of Compromise (IoCs)
|
Category |
Indicator |
Description |
|
Domain |
npm-cache[.]com |
Primary C2 and data exfiltration domain; served via Cloudflare |
|
Domain |
registry[.]npmjs[.]org |
Token validation endpoint abused for live credential verification prior to exfiltration |
|
Domain |
eth-mainnet[.]nodereal[.]io |
Ethereum RPC endpoint used for smart-contract-based C2 domain retrieval |
|
Domain |
go[.]getblock[.]io |
Ethereum RPC endpoint used for smart-contract-based C2 domain retrieval |
|
Domain |
eth[.]llamarpc[.]com |
Ethereum RPC endpoint used for smart-contract-based C2 domain retrieval |
|
Domain |
pypi-get[.]com |
Additional attacker-controlled infrastructure |
|
Domain |
js-mirror[.]com |
Additional attacker-controlled infrastructure |
|
IPv4 |
104[.]21[.]35[.]216 |
Cloudflare IP associated with npm-cache[.]com |
|
IPv4 |
172[.]67[.]167[.]200 |
Cloudflare IP associated with eth[.]llamarpc[.]com |
|
IPv4 |
185[.]44[.]207[.]215 |
IP associated with go[.]getblock[.]io ETH RPC |
|
IPv4 |
35[.]175[.]164[.]77 |
AWS IP associated with eth-mainnet[.]nodereal[.]io |
|
Filename |
Math_Symbol.js |
Obfuscated infostealer payload (727,680 bytes); deployed via npm package preinstall scripts |
|
Filename |
math_init.js |
Variant of the infostealer payload with identical functionality |
|
Filename |
setup.mjs |
Stage-1 dropper; downloads Bun runtime and triggers infostealer execution |
|
File Hash (SHA-1) |
35a672cf34b996b91f3e1c28cbf3a05a37e036e4 |
Math_Symbol.js / math_init.js infostealer payload |
|
File Hash (SHA-1) |
686aa40d0fc22c8d569494543a0f891f359f2f99 |
setup.mjs located in .claude directory (Claude Code hook) |
|
File Hash (SHA-1) |
f525d52ceb966516686b482d3dc0137028cc6a63 |
setup.mjs located in .vscode directory (VS Code hook) |
|
User-Agent |
Bun/1.3.13 |
HTTP user-agent string used during Bun runtime download and C2 communications |
|
Filesystem Path |
/tmp/bun-dl-*/ |
Temporary directory used for Bun runtime download and staging |
|
Filesystem Path |
node_modules/keyv/Math_Symbol.js |
Location of the infostealer payload within the installed package tree |
|
String |
Shai-Hulud: Here We Go Again |
GitHub repository description used for exfiltration repos |
|
String |
IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients |
Intimidation string in initial exfiltration commits |
Detections and Threat Hunting
Security teams should implement the following detection measures:
Mitigations and Remediations
Immediate Actions
System Recovery
Cloud and Source Code Review
Supply Chain Hardening
If you are worried about any of the threats outlined in this bulletin or need help in determining what steps you should take to protect yourself from the most material threats facing your organisation, please contact your account manager, or alternatively get in touch to find out how you can protect your organisation.