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.
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.
Entry: keyv maintainer, method not published
Reach: over 1.3 billion monthly downloads across co-owned packages
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.
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
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.
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
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.
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
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.
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
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.
Domains observed: awqhnjewqjkl[.]icu, npm-cache[.]com
Fallback: GitHub commit search for a signed marker
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.
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
🛡 How to Defend Against This Chain
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.preinstall hooks by default. Upgrade, and delay adoption of newly published versions, so that malicious releases are caught before they reach your builds..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.~/.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.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.