A generated abstract illustration on a deep navy field: a chain of interlocking translucent blocks runs left to right, each block stamped with a small glowing cyan seal. Near the middle of the chain, one block has been swapped for a dark block carrying an identical cyan seal, and the chain continues past it without a break. Thin amber filaments branch downward from that dark block toward a small row of dark terminal panes at the bottom of the frame. No text, numbers, people, brand marks or organisation logos appear. The OrcaRouter logo is composited in the bottom-right corner.
AI Safety Incidents

ChainDrop: valid provenance in 400+ npm packages, and Claude Code hooks that need no npm install

Author

Elias Hawthorne

Date Published

Latest models · 20View all models →
Benchmarks: Artificial Analysis · updated daily
Back to all posts

On 4 August 2026 attackers took over the GitHub account behind keyv, a caching library that Elastic Security Labs says was downloaded more than 600 million times the previous month. Wiz Research and Orca Security both record that malicious files were pushed straight to keyv's main branch, and that the project's own GitHub Actions workflow then built and published the poisoned release — so it reached npm carrying valid provenance, the attestation supply-chain tooling uses to decide a package is trustworthy. A self-propagating worm researchers call ChainDrop then used stolen npm tokens to repeat the trick across other maintainers' packages: Elastic counted more than 400 unique packages by 6 August, Microsoft Threat Intelligence described the same campaign on 4 August, and Singapore's Cyber Security Agency counted over 1,300 compromised package versions with a combined 2 billion monthly downloads. Elastic and Microsoft both describe hooks that re-run when a repository is opened in Claude Code or Visual Studio Code. The record's full source list, severity grade and confidence assessment sit in the AI incident archive, alongside the other npm and developer-tooling supply-chain entries filed around it.

How a legitimate release pipeline signed its own compromise

Wiz Research reconstructs the opening minutes: from 09:00 UTC on 4 August the attacker used a compromised identity to introduce IDE persistence payloads into the keyv repository, and then, shortly after, published a new version of keyv containing the payload. Wiz names the compromised party as a GitHub maintainer account and says multiple packages in the keyv and cacheable ecosystem followed.

Orca Security's rapid-response write-up is more specific about the signing path. The attacker, it says, pushed two malicious files — setup.mjs and Math_Symbol.js — directly to the main branch and immediately cut new releases. Because those releases were built through the compromised account's GitHub Actions workflows, the poisoned versions were published to npm with valid provenance signatures, bypassing standard supply-chain integrity checks. Zscaler ThreatLabz, an independent research team that is not among the record's cited sources, reaches the same conclusion and puts a level on it: the malicious keyv 6.0.0 carried valid SLSA Build Level 3 provenance because the attacker controlled the source repository. BleepingComputer, which reported the campaign on the day, records the same mechanism — malicious files pushed directly to the projects' main branches, new releases generated, valid provenance because the builds ran through legitimate GitHub Actions workflows.

Microsoft's analysis adds a detail that looks like a contradiction and is not one. In the worm's propagation stage, Microsoft Threat Intelligence found that many malicious versions had no corresponding source-code commit, pull request, tag or legitimate release, which indicated the attackers modified and published package tarballs directly rather than compromising each public source repository. Read together, the sources describe two signing regimes inside one campaign: the first keyv releases travelled through the victim's real CI pipeline and carry attestations that vouch for it, while the worm's later releases were largely assembled and published with stolen registry tokens, leaving no source trail at all. Microsoft's own observation is that the tarball-direct pattern explains why malicious releases frequently appeared as an ordinary patch-version increment without corresponding source commits or pull requests.

The uncomfortable lesson is stated plainly by Zscaler: provenance validates the build process, not the identity of the account or the integrity of the CI system running it. It also concedes a gap that matters for anyone reconstructing the incident — the exact account-takeover mechanism the attacker used was unknown at the time of its analysis.

What runs on npm install: preinstall, Bun 1.3.13, and a 300-pattern collector

Elastic Security Labs describes the trigger as a preinstall hook in package.json, which abuses a legitimate npm feature that runs arbitrary commands before a package finishes installing, requiring no further interaction from the victim. The hook launches setup.mjs, which checks whether the Bun JavaScript runtime is already present. If it is not, the dropper detects the machine's platform and architecture, downloads Bun v1.3.13 from the official release page, extracts it, uses it to execute the payload, and then deletes the temporary directory to cover its tracks. Wiz independently identified the user agent Bun/1.3.13 making the malicious calls. The dropper is cross-platform, targeting Linux, macOS and Windows.

The payload is heavily obfuscated, and the sources differ slightly on its size: Elastic puts it at 711 kilobytes, Orca Security at 728 KB. It uses control-flow flattening and a Base91 string-encoding scheme, and carries Dune-themed strings — laza, kanly, ghola, mentat, lasgun, sietch, fedaykin, tleilaxu, sandworm, sardaukar, ornithopter, navigator — which Elastic notes has similarities with earlier Shai-Hulud campaigns.

Microsoft's behavioural account adds two details worth having. The payload checks its environment first and exits on Russian-language systems, avoids duplicate instances, and starts a detached background copy on developer workstations; inside CI it stays attached to the active job so it can reach workflow secrets, runner credentials and OpenID Connect publishing permissions.

The collector is where the incident becomes an AI-tooling story rather than a generic credential story. Elastic says it scans over 300 unique patterns across credential stores on a developer machine, and that the notable targeting is aimed at AI tooling credentials: Anthropic, Claude, Codex, Cursor, OpenAI and Gemini. It also takes AWS, GCP, Azure and Alibaba Cloud credentials, GitHub personal access tokens, JWT and session tokens, HashiCorp Vault tokens, SSH private keys, Kubernetes service account tokens and npm tokens. Wiz reports the target list had been expanded by almost 70% relative to the earlier Mini Shai-Hulud code it is built on, with new additions including AI-agent credential stores for Claude, OpenAI, Codex, Cursor and Gemini, cryptocurrency keystores for Foundry, Solana and Monero, Jenkins' master.key, Argo CD and Harbor secrets, Alibaba and Tencent CLI configurations, and /etc/shadow.

Both Microsoft and Elastic describe the same exfiltration design. Results are serialised as JSON, gzip-compressed and encrypted with a randomly generated AES-256-GCM key, and that key is wrapped with the attacker's RSA public key. The payload does not hardcode its command-and-control domain: Elastic says it queries an Ethereum smart contract at 0xE1f2395ee43e45A1556EC6438a88c31B83493103 to fetch the current endpoint at runtime, so infrastructure can be rotated without touching the malware. If that fails it looks for a cryptographically signed marker — thebeautifulmarchoftime — in GitHub commit history and validates the domain against an embedded RSA key. If both fail, it exfiltrates through a public GitHub repository the victim's own account creates, described "Shai-Hulud: Here We Go Again". Microsoft notes one fallback path encodes a stolen GitHub token with double Base64 rather than encrypting it.

Propagation is gated on token quality. Elastic reports the worm activates only when the credential sweep finds an npm token with package write permission and the ability to publish without two-factor authentication; it then downloads each writable package's latest tarball, injects itself, bumps the patch version and republishes. BleepingComputer adds that every token is validated in real time against the registry's whoami endpoint before it is stolen. That routine is what turned one compromised identity into hundreds of releases, and it is why the malicious versions look like ordinary patch bumps.

An HTML card titled 'The first four hours, 4 August 2026', subtitled 'What the sources put where in the infection order, and the three counts the campaign is quoted with'. A four-step numbered column headed 'Infection order' reads: 1, 09:00 UTC: files pushed to main — attacker adds IDE persistence payloads to the keyv repository, then publishes a new keyv version, per Wiz Research; 2, the victim's CI builds the payload — keyv 6.0.0 reaches npm through the compromised account's own GitHub Actions workflow carrying valid provenance and a SLSA Build Level 3 attestation, per Orca Security and Zscaler ThreatLabz; 3, the worm republishes with stolen tokens — stolen npm tokens with write access and 2FA bypass bump patch versions, at peak roughly one infected package per second, per Elastic Security Labs and Zscaler ThreatLabz; 4, IDE hooks travel with the repos — a stolen GitHub App token commits .claude/settings.json and .vscode/tasks.json to up to 50 branches per accessible repository, per Elastic Security Labs. A right-hand panel headed 'Three counts, three units' shows: 400+, unique npm packages compromised by 6 August, per Elastic and Microsoft Threat Intelligence; 868, worm-reached packages across 1,381 versions, per Aikido via BleepingComputer and Orca Security; 1,300+, package versions with 2 billion monthly downloads, per CSA Singapore advisory AD-2026-009, 6 August. A footer reads: each figure is a snapshot taken while the campaign was still running; none of the cited sources claims a final total.

The infection path that needs no npm install

The finding that separates this campaign from earlier npm worms is that installation is no longer the only way in. Elastic describes two additional execution paths aimed at users of AI coding tools. A SessionStart hook in .claude/settings.json runs node .claude/setup.mjs when a new Claude session starts. For VS Code, a folderOpen task in .vscode/tasks.json runs node .vscode/setup.mjs when an infected repository is opened. Microsoft's analysis lists the same four artefacts — .claude/settings.json, .claude/setup.mjs, .vscode/tasks.json and .vscode/setup.mjs — and describes them as a secondary infection route that can restart the payload after the original npm installation has completed.

The condition that widens this dramatically is in the stolen credentials. When a GitHub App token is among them, Elastic reports the worm commits the malicious hooks to up to 50 branches per accessible repository, injecting .claude/settings.json and .vscode/tasks.json into each. Elastic's conclusion is that a developer who clones one of those branches and opens it can be infected by opening the project — no package installation, and therefore no install-script defence, in the path at all.

Zscaler's assessment is that the persistence hooks survive package removal and re-execute simply by opening the project in VS Code or Claude Code, which bypasses the --ignore-scripts mitigation that many teams adopted after earlier npm worms. Its recommendation follows directly: treat IDE and AI-agent configuration files — .claude/, .cursor/, .vscode/, .gemini/ — as executable code, reviewed with the same rigour as source.

There is a fingerprint for the repository half of the attack. Elastic reports that worm-generated commits can be identified by the author name claude and the commit message chore: update config, and Wiz records the same message string. Elastic adds that the commit message also carried the stolen GitHub token alongside an intimidation string — IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients — which it reads as an attempt to scare developers out of revoking the token. Wiz records the same string in the initial commits, with the shorter message switched in on the commits that introduce the IDE hooks.

A generated abstract illustration on a deep navy field: a tall repository-shaped block stands at centre, its face divided into thin horizontal branch lines. A narrow beam of pale light from an editor-shaped pane at upper left falls across the block, and where the beam touches one branch line, a small bright hook symbol appears and a thin amber thread runs down into a dark pool at the base. The other branch lines remain untouched and faintly lit. No text, numbers, people, brand marks or organisation logos appear. The OrcaRouter logo is composited in the bottom-right corner.

The counts do not agree, and that is not a contradiction

Every published figure for the blast radius is attached to a different unit, and the units are not interchangeable. Elastic Security Labs, writing on 6 August, counted over 400 unique npm packages compromised. Microsoft Threat Intelligence described more than 400 packages across multiple unrelated publishers. Aikido, as quoted by BleepingComputer, put it at at least 868 packages across 1,381 versions. Orca Security reports 10 directly poisoned keyv and cacheable packages with over 619 million monthly downloads, then 868 additional packages and 1,381 versions reached by the worm, for more than 2 billion monthly installs. Singapore's CSA advisory AD-2026-009, dated 6 August, records over 1,300 compromised package versions representing a combined 2 billion monthly downloads.

Those are three different denominators — packages, package versions and monthly download volume — taken at three different moments of a campaign that was still running. Zscaler's account of the speed explains why they diverge: it says the worm self-propagated to more than 400 packages in under four hours and, at peak, published roughly one infected package per second. Treating a later count as a correction of an earlier one would be wrong; each is a snapshot, and none of the cited sources claims a final total.

Elastic's exposure figures at the library level are the ones most often quoted and the least ambiguous. It reports keyv alone at over 600 million downloads the previous month, with flat-cache near 580 million, cacheable-request over 137 million, cacheable over 30 million and cache-manager over 16 million. CSA's advisory lists the specific compromised versions, among them keyv 6.0.0, flat-cache 6.1.24, file-entry-cache 11.1.6, cacheable-request 13.0.20, cacheable 2.5.1, cache-manager 7.2.10, @cacheable/utils 2.5.1 and ecto 5.0.1.

The revocation trap

Responders who installed an affected version face a second problem after the first. Elastic reports that in a conditional GitHub fallback path the payload attempts to install a token-monitor component that maintains credential access and contains a destructive handler if the monitored token is revoked. Zscaler describes the mechanism in operational detail and names it a token revocation trap: the worm installs a background process polling the GitHub API every 60 seconds with the stolen token, and if the token returns a 4xx error — meaning the victim has revoked it — a secondary destructive payload fires, which depending on the variant can include remote code execution or deletion of the user's home directory.

Wiz adds that the dead-man's switch is selectively armed based on a response from the command-and-control server: the operator can return a value specifying which command to arm and which token the switch should watch for revocation. Wiz also notes the monitor looks for a valid response from the user endpoint, so any token type valid there can serve as the trigger.

The practical consequence is recorded by BleepingComputer and by Aikido's guidance it quotes: a developer workstation or CI/CD runner that installed an affected version should be treated as compromised even if the package was subsequently removed, because second-stage execution can have exposed tokens and undermined build integrity. Microsoft's guidance reaches the same place from the build side, warning that organisations which installed an affected package with lifecycle scripts enabled should treat the workstation or build runner as potentially compromised and rebuild affected systems and downstream artefacts from trusted sources.

An HTML card titled 'Established, assessed, and not shown', subtitled 'The same five analyses, sorted by how strongly each claim is supported — not by how confidently it is written'. Three columns. ESTABLISHED lists: the keyv maintainer's GitHub account was compromised, files were pushed to main and a release was published on 4 August 2026; at least keyv 6.0.0 shipped through the victim's own CI with valid provenance and SLSA Build Level 3; a preinstall hook ran setup.mjs, which downloaded Bun v1.3.13 to execute an obfuscated payload; the collector scanned 300+ credential patterns including Anthropic, Claude, Codex, Cursor, OpenAI and Gemini; hooks in .claude/settings.json and .vscode/tasks.json run on session start and folder open; a GitHub App token extended the hooks to up to 50 branches per accessible repository; more than 400 unique packages were compromised within days; CSA Singapore published advisory AD-2026-009 on 6 August 2026. ASSESSED lists: attribution to the Mini Shai-Hulud family and a TeamPCP lineage; that the account takeover was the initial access route; that provenance must not be read as proof of safety, since it validates the pipeline and not the account; that IDE configuration files should be treated as executable code. NOT SHOWN lists: how the maintainer account was taken over, reported as unknown by Zscaler; any single final total, since the counts use packages, versions and downloads interchangeably; any confirmed reuse of a stolen AI-tooling credential against a provider account; who the operator is beyond vendor attribution; any remediation outcome, account recovery or enforcement action. A footer cites Elastic Security Labs, Microsoft Threat Intelligence, Wiz Research, Orca Security and CSA Singapore AD-2026-009.

What is established, what is assessed, and what nobody has shown

The primary record and five independent analyses converge on the mechanics, and the mechanical claims are the strongest part of the file.

• The GitHub account of the keyv maintainer was compromised, and malicious files were pushed directly to main before a release was published, on 4 August 2026 — reported consistently by Wiz, Orca Security, BleepingComputer and CSA Singapore.

• At least keyv 6.0.0 reached npm through the victim's own CI pipeline carrying valid provenance, with Zscaler specifying a SLSA Build Level 3 attestation. This is the claim that makes the incident notable, and it is corroborated by three organisations that did not co-author each other's work.

• A preinstall hook executed setup.mjs, which downloaded Bun v1.3.13 and ran an obfuscated payload that scanned over 300 credential patterns, including AI coding-tool credentials — Elastic and Wiz agree on both the runtime version and the AI-targeted credential list.

• Hooks written to .claude/settings.json and .vscode/tasks.json execute on session start and on folder open, and a GitHub App token extended that to up to 50 branches per accessible repository — Elastic's finding, and the basis for its statement that a developer can be infected by opening a repository.

• More than 400 unique packages were compromised within days, and CSA Singapore, a national regulator, published advisory AD-2026-009 on 6 August 2026 listing affected versions and indicators.

What the sources treat as assessment rather than fact should be labelled. Wiz describes the payload as a descendant of the "Mini" Shai-Hulud malware family, built on public Shai-Hulud-Open-Source repositories published by TeamPCP, with a few crucial changes; Orca Security attributes the attack to TeamPCP; Zscaler links ChainDrop to TeamPCP and places it in a lineage running from September 2025. That is a vendor attribution consensus, not something a cited primary document proves about a named individual or group.

Three things no cited source establishes, and this piece does not fill in. First, how the maintainer account was taken over: Zscaler states plainly that the exact mechanism was unknown at the time of its analysis. Second, a single final total: the 400, 868 and 1,300 figures count packages, packages-and-versions and versions at different moments, and no source claims the campaign is closed. Third, and most important for the AI-specific claim in this record, no cited source reports a confirmed instance of a stolen Anthropic, Codex, Cursor, Gemini or OpenAI credential being replayed against a provider account. What is documented is intent and capability — an explicit target list, and a collector built to reach it — not a demonstrated downstream breach of an AI provider's systems.

What changed after — and what a responder has to do

The most concrete remedy in the record is a platform change. Elastic recommends upgrading to npm 12 or later, which blocks preinstall hooks by default; Microsoft's guidance independently recommends npm CLI v12 and the min-release-age feature, which hold new versions back long enough for the community to catch a compromise before it reaches a build.

Beyond that, the response steps the sources publish overlap heavily.

• Elastic: revoke all GitHub tokens including session tokens on affected machines; revoke and regenerate npm tokens, especially automation tokens with write permission and two-factor bypass; search repositories for unauthorized commits with the message chore: update config from claude@users.noreply.github.com; rotate cloud, Kubernetes, Vault, AI-tooling and CI/CD secrets; enable two-factor authentication on npm accounts.

• Microsoft: purge npm and yarn caches on affected endpoints and build hosts, pin known-good versions, review lockfiles and artefacts for the compromised versions including transitive references, rebuild shared base images and golden build runners, and review token scopes, workflow approvals and release provenance because the incident is consistent with CI/CD pipeline abuse through OIDC publishing.

• CSA Singapore: remove affected versions from development, build and CI/CD environments, treat affected systems as compromised and rebuild them, rotate credentials, review cloud and source-code environments for unauthorized access, monitor for published indicators, and strengthen allowlisting, integrity verification and provenance controls.

• Zscaler: treat .claude/, .cursor/, .vscode/ and .gemini/ as executable code, and stop treating SLSA or Sigstore provenance as proof of safety — layering it instead with anomaly detection on publishing behaviour such as off-hours publishing, bulk version publishing and first-time publishers.

The record does not contain a remediation outcome. No cited source reports the malicious versions fully removed, an account recovery, an arrest or a regulatory enforcement action; CSA's advisory is a notice to defenders, not a finding against a party.

What the incident changes is a classification. A developer-tool configuration file is an execution path, and an editor opening a folder is an execution event. And a provenance attestation is a statement about which pipeline produced a package — not about who was holding the keys when that pipeline ran, which is precisely the gap the first keyv release fell through.