Get the Zoom link
Cloud Security Office Hours Banner

CHAINDROP / keyv npm Worm 2026

Step-by-step kill chain mapped to MITRE ATT&CK Cloud, sourced from official post-mortems and primary technical analyses.

August 2026 Critical npm

CHAINDROP / keyv - A Compromised Maintainer → preinstall Hook and a Downloaded Bun → 300+ Credential Patterns → Self-Publishing Worm → A Dead-Man's Switch on Token Revocation

Elastic Security Labs identified this campaign on August 4, 2026. Attackers compromised the maintainer of keyv, a key-value storage library, and trojanized the monorepo. By August 6 more than 400 unique npm packages had been compromised, and Elastic puts the co-owned packages at over 1.3 billion monthly downloads combined. How the maintainer was compromised has not been published. Elastic links the campaign to the earlier Shai-Hulud attacks through similarities and Dune-themed strings, and names no actor. Elastic ties its npm republishing to tokens that can publish without 2FA, and Socket also reports spread through OIDC trusted publishing. It plants hooks in Claude Code and VS Code configuration, so that opening an infected repository can run it, and it keeps no hardcoded command-and-control domain, reading its current endpoint from an Ethereum smart contract. It also leaves a dead-man's switch: a watcher that runs a remote-supplied handler when a stolen token is revoked. If you think you are affected, read step 05 before you revoke anything.

400+ packages compromised by August 6, per Elastic
1.3 billion+ combined monthly downloads of the co-owned packages
300+ patterns of credentials targeted across AI, cloud and developer tools
Threat actor: unnamed; linked to Shai-Hulud by similarities and strings
📄 Elastic Security Labs - CHAINDROP analysis ↗ 📄 SANS ISC - The dead-man's switch and the response order ↗
⚡ Initial Access: The keyv Maintainer
01
Attackers compromised the keyv maintainer and trojanized the monorepo
T1195.002 - Compromise Software Supply Chain

How the maintainer's account was compromised has not been published. Socket says the malicious versions went out through the legitimate release workflow, which built already-trojanized source, so the signed provenance was valid. The choice of target did the rest. By Elastic's figures keyv saw over 600 million downloads in the prior month, flat-cache nearly 580 million, cacheable-request over 137 million, cacheable over 30 million and cache-manager over 16 million.

These are small utility packages that sit deep in other projects' dependency trees, so most of the people installing them never chose them.

Identified: August 4, 2026, by Elastic Security Labs
Entry: keyv maintainer, method not published
Reach: over 1.3 billion monthly downloads across co-owned packages
keyvMaintainer CompromiseT1195.002
🔀 Execution: preinstall, Then Bun
02
A preinstall hook ran a dropper that fetched its own Bun runtime, then deleted it
T1059.007 - JavaScript

A preinstall hook in package.json runs during installation with no further interaction. Each affected package carries setup.mjs, which targets Linux, macOS and Windows. If Bun is missing, the dropper downloads v1.3.13 from Bun's official GitHub releases, runs the payload, and deletes the temporary directory.

The payload is about 711 KB, obfuscated with control-flow flattening and Base91 string encoding, and named Math_Symbol.js or math_init.js. Fetching a trusted runtime from an official source blends into normal developer traffic, and it means the malware does not need Node-specific tricks.

Trigger: preinstall hook in package.json
Dropper: setup.mjs for Linux, macOS and Windows
Runtime: Bun v1.3.13 from official releases, deleted afterwards
Payload: about 711 KB, control-flow flattening, Base91
preinstallBunObfuscationT1059.007T1027
🔑 Credential Access: 300+ Patterns
03
A collector swept over 300 credential patterns, with AI tools on the same list as cloud keys
T1552.001 - Credentials in Files

The collector scans for over 300 unique patterns. The list covers AI tooling (Anthropic, Claude, Codex, Cursor, OpenAI, Gemini), cloud accounts (AWS, GCP, Azure, Alibaba Cloud), GitHub tokens, HashiCorp Vault tokens, SSH private keys, Kubernetes service account tokens and npm tokens.

Stolen data is gzip-compressed and encrypted with a random AES-256-GCM key, which is itself encrypted with the attacker's hardcoded RSA public key. A developer's laptop holds all of these credentials at once, which makes it the most efficient place to steal from.

Patterns: over 300 unique
Targets: AI tools, AWS, GCP, Azure, Alibaba Cloud, GitHub, Vault, SSH, Kubernetes, npm
Protection: AES-256-GCM, with the key wrapped by a hardcoded RSA public key
Credential SweepAI Tool KeysT1552.001T1552.004
🔀 Propagation: Republish on bypass_2fa
04
Stolen npm tokens able to publish without 2FA turned each victim into the next spreader
T1195.001 - Compromise Software Dependencies and Development Tools

Elastic says the npm republishing activates only when a stolen token has package write permission and can publish without 2FA, the bypass_2fa setting, and Socket also reports republishing through npm OIDC trusted publishing. For each writable package it downloads the latest tarball, injects its files, adds the preinstall hook, bumps the patch version and publishes it back.

Commit messages carry the stolen GitHub token and the string IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients. Elastic reads that as an attempt to discourage revocation. Do not dismiss the idea that revocation has consequences, though, because step 05 describes a real trigger. Worm commits use the author claude and the message chore: update config.

Condition: npm token with write permission and bypass_2fa
Loop: download tarball, inject, add hook, bump patch, publish
Commit signature: author claude, message chore: update config
Deterrent string: tells defenders that revoking will crash production
npm Wormbypass_2faT1195.001
🔐 Persistence: Developer Tool Hooks and a Dead-Man's Switch
05
Hooks in Claude Code and VS Code run the dropper, and a watcher waits for the stolen token to be revoked
T1546 - Event Triggered Execution

A SessionStart hook in the Claude Code settings.json runs node .claude/setup.mjs when a session begins. A folderOpen task in the VS Code tasks.json runs node .vscode/setup.mjs when an infected repository is opened.

If the worm steals a GitHub App token, it commits these hooks to up to 50 branches per accessible repository. That extends the attack beyond package installation: a developer who only clones and opens a repository can be infected, and removing the npm package leaves the hook in place.

The host also gets a watcher, installed as a macOS LaunchAgent or a Linux systemd user service and named to look like a GitHub token monitor. SANS says it polls the GitHub API with the stolen token every 60 seconds. When the token stops working, which is what revocation produces, it evaluates a remote-supplied handler, deletes its own state and exits. It also removes itself after 24 hours.

Nobody outside the operation can say what that handler does, because it is fetched at runtime and can change. SANS lists data destruction and re-implantation among the possibilities. Elastic's report does not cover the watcher, and SANS and Socket both say to deal with it before revoking credentials.

Claude Code: SessionStart hook in settings.json
VS Code: folderOpen task in tasks.json
Spread: up to 50 branches per repository with a GitHub App token
Watcher: gh-token-monitor, polls GitHub every 60 seconds, 24-hour lifetime
Trigger: an HTTP 4xx on the token, which is what revocation returns
Handler: remote-supplied at runtime, contents unknown
Claude CodeVS CodeRepository Hooksgh-token-monitorT1546T1543.002
🔀 Command and Control: A Dead Drop on a Blockchain
06
The worm reads its current exfiltration endpoint from an Ethereum smart contract
T1102.001 - Web Service: Dead Drop Resolver

CHAINDROP has no hardcoded C2 domain. It queries contract 0xE1f2395ee43e45A1556EC6438a88c31B83493103 through multiple RPC providers, so operators can move infrastructure by updating the contract. Elastic saw awqhnjewqjkl[.]icu and npm-cache[.]com during detonation.

A takedown can remove a domain, and it cannot remove a contract from Ethereum. As a fallback the malware searches GitHub commit history for the signed marker thebeautifulmarchoftime and accepts a domain only after validating it against an embedded RSA public key, so a defender cannot simply plant a fake one.

Contract: 0xE1f2395ee43e45A1556EC6438a88c31B83493103
Domains observed: awqhnjewqjkl[.]icu, npm-cache[.]com
Fallback: GitHub commit search for a signed marker
EthereumDead DropSigned FallbackT1102.001
📤 Exfiltration: Endpoint First, Victim's GitHub Last
07
Data goes to the dead-drop endpoint, or failing that to a public repository under the victim's own account
T1567.001 - Exfiltration to Code Repository

The primary path is the endpoint read from the contract. If the earlier paths fail, data goes to a public GitHub repository created under the victim's account, named with Dune references and described as Shai-Hulud: Here We Go Again. That description is a family marker, and searching for it is a quick exposure check.

Elastic describes no destructive payload, and the one anti-forensic step it documents is removing the temporary Bun directory. The open question is the watcher in step 05, whose handler is supplied by the attacker at runtime.

Primary: endpoint from the dead drop
Fallback: public repository under the victim's GitHub account
Marker: description Shai-Hulud: Here We Go Again
Destruction: none described by Elastic; the watcher's handler is unknown
Public RepositoryShai-Hulud MarkerT1567.001

🛡 How to Defend Against This Chain

Remove tokens that can skip 2FA. Elastic ties npm republishing to tokens with write access and bypass_2fa. Enable 2FA on all npm accounts and retire those automation tokens wherever you can. Trusted publishing removes the long-lived token, but Socket reports this worm republishing through it as well, so restrict which workflows and branches may publish.
Block install scripts and add a soak period. Elastic notes that npm 12 and later block preinstall hooks by default. Upgrade, and delay adoption of newly published versions, so that malicious releases are caught before they reach your builds.
Review developer tool configuration like code. Check .claude/settings.json and .vscode/tasks.json in repositories and in pull requests, and protect those paths with code owners. Search for commits from claude@users.noreply.github.com with the message chore: update config that nobody can account for.
Isolate the host before you revoke anything. Revoking a token while the watcher is running can fire its handler. SANS gives the order as isolate, preserve, eradicate, rotate, audit: cut the host from the network, which stops the watcher seeing a 4xx, then copy ~/.config/gh-token-monitor/ and the service files before they clear themselves, remove the watcher, hooks and payload, and only then revoke npm, GitHub, cloud, Kubernetes, Vault, AI and CI credentials. Socket gives the same order. Kodem's runbook rotates first, on the grounds that the worm's next hop is your publishing token, so isolate quickly and do not let a slow cleanup leave a token live. SANS also says not to power the machine off, because memory is evidence.
Keep cloud keys off developer laptops. Everything on the collector's list is valuable because it sits on one machine. Use short-lived credentials from an SSO flow, and keep long-lived keys out of plaintext dotfiles.
Look for the family's markers. Search your organization for public repositories with the description Shai-Hulud: Here We Go Again, and block npm-cache[.]com and awqhnjewqjkl[.]icu at egress. Alert on Ethereum RPC traffic from build agents, which rarely has a legitimate reason to exist.

Related defense topics