No Install Required: The keyv and cacheable npm Worm That Weaponized AI Coding Agents

Key Highlights

  • What Happened: An attacker hijacked the GitHub account of the maintainer behind keyv and the cacheable family of caching packages and used it to publish a self-propagating, credential-stealing worm to npm. The maintainer is a victim of this incident, not a participant. 11 malicious releases were identified across the maintainer’s 61 packages; hundreds more were republished by the worm within hours.
  • What’s Notable: The poisoned versions shipped with valid, GitHub-signed provenance. The most novel element is the delivery path: persistence is planted in AI-agent and IDE configuration, so a developer or an AI coding agent can be compromised simply by opening the repository, with no npm install required. The malware also encrypts stolen data, fingerprints each host, resolves its C2 from an Ethereum smart contract, and carries a token-revocation kill switch.
  • What You Should Do: Identify affected versions from a verified list and roll back (do not “upgrade to latest,” which on worm-hit names is still poisoned). Treat any host that ran an install or opened a seeded repo as compromised. Neutralize the kill switch and all persistence before rotating credentials, because revocation is the trigger.

What Happened?

Public commit and registry records place the campaign to the minute. At 09:02 UTC on August 4, 2026, an attacker using the compromised jaredwray GitHub identity pushed a malicious commit directly to the main branch of the keyv repository. IDE and agent persistence hooks landed at 09:04, a giveaway test file was removed at 09:23, and a trojanized keyv version was published to npm at 09:35. The cacheable family followed between 10:09 and 10:14, and ecto at 10:28. The maintainer, Jared Wray, is an incident victim: the initial access vector to the account remains unconfirmed.

Like the earlier Shai-Hulud waves Cycode has tracked, this attack does not exploit a flaw in npm itself. It abuses a trusted maintainer identity and legitimate publishing workflows to deliver malicious code into developer machines, CI/CD systems, and enterprise build pipelines. keyv alone carries roughly 127 million weekly npm downloads (a measure of reach, not of compromised hosts), and sibling packages such as flat-cache and file-entry-cache each move hundreds of millions more per month.

How It Actually Reaches You: a Semver Inversion

The widely repeated “ESLint pulls keyv transitively” framing is misleading for this incident. Nothing common in the dependency tree declares a range that accepts keyv 6.x, so the malicious [email protected] only reached people who explicitly ran npm i keyv@latest or had pinned to the 6 line. The silent installs came from the opposite direction: patch bumps published inside version ranges teams already had. If your manifest carried a caret range on any affected sibling, a routine npm install re-resolved straight into the malicious patch.

Package Common declared range Malicious version Silently installed?
cacheable-request ^13.0.18 13.0.20 Yes: patch bump inside the caret range
cache-manager ^7.2.9 7.2.10 Yes: patch bump inside the caret range
@cacheable/utils ^2.5.0 2.5.1 Yes: patch bump inside the caret range
keyv ^5.6.0 6.0.0 No: major bump, reached only explicit installs
adadad

What Makes This Attack Different

This is not a straight replay of earlier Shai-Hulud waves. The operator re-tooled almost every stage, combining evasions that defeat the controls teams usually rely on:

  • Provenance-signed malicious releases. The attacker pushed to the repo and released through its own GitHub Actions pipeline, so the poisoned versions carried valid attestations. Provenance verification alone could not catch this, because the build faithfully attested a source that was already compromised.
  • Bun-runtime execution. The preinstall hook runs an obfuscated setup.mjs that downloads a standalone Bun binary and executes the payload under it, sidestepping the host Node version and Node-level monitoring. Because the download comes from Bun’s official GitHub releases, there is no suspicious domain for egress monitoring to flag. A single-instance lock file, tmp.dpkg_14527.lock, prevents re-entry while the payload is resident.
  • On-chain C2 resolution. Rather than hardcoding infrastructure, the malware resolves its C2 domains from an Ethereum mainnet smart contract of type StringListStore at address 0xE1f2395ee43e45A1556EC6438a88c31B83493103, queried via eth_call with selector 0x53ed5143 across roughly 75 hardcoded public RPC endpoints. Results are posted over HTTPS to a /router path. On-chain history shows the contract was first seeded with three domains before being narrowed to a single exfil host, npm-cache[.]com (registered 2026-05-22). The contract owner was funded by an address previously flagged for scam activity.
  • Hybrid-encrypted exfiltration with per-host fingerprinting. Stolen data is serialized, gzipped, and encrypted with a random AES-256-GCM session key, which is then wrapped with the operator’s embedded RSA public key. Each victim is tagged with a unique machine fingerprint, a SHA-256 hash over a concatenation of system properties, most likely to enable targeted, per-machine payload delivery. A successful C2 response can include a code field that the payload passes to eval, giving the operator full remote code execution inside the compromised process.
  • A revocation-triggered kill switch. A watcher, installed as a Linux systemd user service or a macOS LaunchAgent, polls api.github.com/user with a stolen token roughly every 60 seconds. On the first 4xx (that is, the moment you revoke the token), it evaluates an attacker-supplied handler string, deletes its state, and exits. Blind, large-scale credential rotation is therefore dangerous here: revocation is the trigger.
  • Clean library, poisoned lifecycle. The compiled dist/ output is byte-identical to the immediately preceding release candidate (6.0.0-rc.1), not to the last clean stable release, which was 5.6.0 on a different major line. All malicious behavior lives in the added install hook and payload files, so the package still functions while the host is compromised.

The AI-Agent Attack Path

The single most novel element of this campaign is that AI coding tools are both a target and an execution primitive.

As an execution primitive, the seed packages plant autostart hooks in .claude/settings.json and .vscode/tasks.json. The two are cross-wired: .claude/settings.json invokes .vscode/setup.mjs, and .vscode/tasks.json invokes .claude/setup.mjs. Opening the repository folder in VS Code, or starting a Claude Code session, executes the loader. Two of the three triggers never touch node_modules, so a developer can be compromised without ever running npm install. A responder who cleans only one of the two directories will believe they are finished while the other path remains live: both must be removed.

As a target, the harvester now sweeps AI-agent credential stores for Claude, OpenAI, Codex, Cursor, and Gemini alongside traditional secrets. Compounding the misdirection, the malicious commits are authored under a string referencing claude, so author-based triage points investigators at an AI agent rather than the compromised human account. Teams that triage by commit author will chase the wrong lead.

adadad

What the Malware Steals

The credential-harvesting surface has expanded significantly over prior variants, reaching well beyond cloud keys:

  • Cloud: AWS, GCP, and Azure instance metadata and IAM credentials, now joined by Alibaba Cloud and Tencent Cloud CLI configurations.
  • Secrets infrastructure: HashiCorp Vault tokens and Kubernetes service-account tokens.
  • CI/CD and registries: GitHub Actions OIDC tokens and org/repo secrets, npm tokens, and self-hosted Jenkins master.key, Argo CD, and Harbor secrets.
  • AI-agent credential stores: configuration and keys for Claude, OpenAI, Codex, Cursor, and Gemini.
  • Cryptocurrency keystores: wallets plus Foundry, Solana, and Monero key material.
  • System files: sensitive host files including /etc/shadow.

Harvested data is committed to attacker-created GitHub repositories described as “Shai-Hulud: Here We Go Again,” and any stolen npm token is used to spread the payload further.

What Actions Should You Take?

If your organization uses npm, act now to assess exposure and secure your environments.

Audit and Remove Affected Packages

  • Check what is installed rather than what is declared: run npm ls keyv flat-cache file-entry-cache cacheable-request cacheable cache-manager @cacheable/utils @cacheable/memory @cacheable/node-cache @cacheable/net ecto --all, then grep lockfiles and CI logs for the malicious versions below.
  • Roll back; do not upgrade. npm reverted dist-tags on only three versions, so latest on most worm-hit names is still poisoned. Remediation here is a pinned rollback, not “move to current.”
  • Block at the version level from a verified list, not the scope level. The @keyv scope was not compromised (its v6 packages published clean between 09:30 and 09:32), so a @keyv scope block breaks builds for zero security gain.

Affected Versions and Safe Rollback Targets

11 malicious releases were identified across the maintainer’s 61 packages. Any list that omits the late-published [email protected] is incomplete.

Package Malicious version Safe rollback target
keyv 6.0.0 5.6.0 (or 6.0.0-rc.1)
cacheable 2.5.1 2.5.0
cacheable-request 13.0.20 13.0.19
cache-manager 7.2.10 7.2.9
flat-cache 6.1.24 6.1.23
file-entry-cache 11.1.6 11.1.5
@cacheable/utils 2.5.1 2.5.0
@cacheable/memory 2.2.1 2.2.0
@cacheable/node-cache 3.1.2 3.1.1
@cacheable/net 2.1.1 2.1.0
ecto 5.0.1 5.0.0

Contain the Kill Switch and Persistence Before You Rotate

  • Treat any workstation or CI runner that installed an affected version, or opened a seeded repository, as compromised and rebuild it rather than cleaning it in place.
  • Hunt the kill switch first. Remove the revocation watcher before touching any token. Look for ~/.local/bin/gh-token-monitor.sh, ~/.config/gh-token-monitor/, ~/.config/systemd/user/gh-token-monitor.service, and ~/Library/LaunchAgents/com.user.gh-token-monitor.plist. IDE hook removal does not touch this watcher.
  • Remove both persistence directories. .claude/settings.json and .vscode/tasks.json are cross-wired, each invoking the other’s setup.mjs, so cleaning only one leaves a live path.
  • Only then rotate exposed credentials: cloud keys, GitHub tokens, SSH keys, Kubernetes configs, Terraform credentials, and other developer secrets. For npm and GitHub, revoke rather than merely rotate.
  • Clear tmp.dpkg_14527.lock last. It is single-instance re-entry prevention; deleting it while the payload is still resident re-arms execution. If you are rebuilding the host (recommended), you can skip this entirely.

Investigate and Monitor

  • Review cloud and source-code environments for unauthorized access following any install.
  • Review CI logs from August 4, 2026 for eth_call or JSON-RPC requests to public Ethereum endpoints from a build agent, and for POST requests to a /router path.
  • Search your GitHub org for new public repositories described as “Shai-Hulud: Here We Go Again” containing a results/ directory, and audit npm accounts for unexpected published versions.

Harden Your Supply Chain

  • Use npm ci, not npm install, in CI, and add a release-age cooldown. Range re-resolution is mechanically how the three patch bumps spread, and detection landed within hours, so a 24-hour cooldown on newly published versions is the single highest-leverage control available here.
  • Disable install scripts by default (for example, npm ci --ignore-scripts). Note this stops the preinstall path only; it does nothing against the IDE and agent-config triggers, which do not run install scripts.
  • Enable dependency allowlisting, package integrity verification, and provenance controls, and apply egress controls on build agents.
adadad

How Cycode Detects and Stops This

Every static signal in this attack suggested the packages were not malicious. Provenance and attestation were valid, because a compromised maintainer account still produces a correctly signed release. Domain reputation was ineffective because the first-stage download came from github.com and the C2 domain is resolved on-chain at runtime with nothing hardcoded to block. Even --ignore-scripts was insufficient, because two of the three execution triggers never run an install script at all. What catches this class of attack is behavioral, and it is where Cycode focuses:

  • Pipeline runtime detection (Cimon). Cimon monitors build and CI/CD runtime behavior and flags the anomalies this worm generates: a preinstall hook spawning a downloaded Bun binary, a build agent issuing eth_call or JSON-RPC requests to Ethereum endpoints, POSTs to a /router path, and unauthorized workflow or runner creation. These behaviors fire regardless of whether the release was signed.
  • Exposure assessment with SCA. Cycode’s Software Composition Analysis, backed by the Threat Intelligence feed, matches your installed inventory against the verified malicious-version list, including the caret-range patch bumps that a manifest scan alone would miss, and points you to the safe rollback target rather than “latest.”
  • Secrets discovery and prioritization. Cycode’s secrets detection inventories the credential classes this payload targets across the SDLC and, via the Risk Intelligence Graph, ranks them by real exposure and reachability so you can sequence a controlled rotation. This prioritizes by exposure; it does not claim to identify which specific token the C2 armed the switch against, so pair it with the kill-switch removal steps above.
  • Public exposure monitoring. Where enabled, Cycode surfaces attacker-created public repositories tied to your org so you see repo-level impact alongside package-level exposure.

Known Impacts

Category Impact
Initial compromised account jaredwray (maintainer is an incident victim)
Malicious releases 11 across the maintainer’s 61 packages; hundreds more republished by the worm
Package-count estimates 2,000-plus versions across affected scopes
Reach keyv ~127M weekly downloads
Malware family “Mini” Shai-Hulud descendant, significantly re-tooled
Destructive capability Revocation-triggered kill switch plus eval of C2-supplied code (RCE)
Exfiltration AES-256-GCM data wrapped with operator RSA key, to attacker GitHub repos; C2 via Ethereum StringListStore
adadad

Indicators of Compromise

Type Indicator
Second-stage hashes setup.mjs (29,918 bytes) SHA-256 54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668; Math_Symbol.js (727,680 bytes) SHA-256 9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc (identical file also shipped as .claude/math_init.js)
Ethereum contract 0xE1f2395ee43e45A1556EC6438a88c31B83493103, eth_call selector 0x53ed5143
ETH RPC endpoints (CI-log search) eth-mainnet.nodereal[.]io, go.getblock[.]io, eth.llamarpc[.]com
Exfil / C2 domain npm-cache[.]com (registered 2026-05-22), contacted at /router
Kill-switch watcher ~/.local/bin/gh-token-monitor.sh, ~/.config/gh-token-monitor/, ~/.config/systemd/user/gh-token-monitor.service, ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
Persistence hooks .claude/settings.json and .vscode/tasks.json (cross-wired, both invoke the other’s setup.mjs)
Lock file tmp.dpkg_14527.lock
Worm injection marker malicious optionalDependencies entry @opensearch/setup added before patch-bump republish (its presence means you became a distributor: check whether you spread it)
User-agent Bun/1.3.13
Attribution strings repo description “Shai-Hulud: Here We Go Again”; the initial-commit string IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients

This is an evolving incident. We will update this post as the research community’s findings develop.