
The keyv supply chain attack, disclosed on August 4, 2026, compromised eleven seed packages across the keyv and cacheable npm namespaces and delivered a Shai-Hulud credential-stealing worm that republished itself into several hundred more packages using stolen npm publishing tokens. keyv, flat-cache, and file-entry-cache alone carry roughly 450 million weekly downloads between them, and the malicious [email protected] shipped with valid npm provenance signed by GitHub Actions. This post is written for AppSec engineers, platform teams, and DevSecOps responders working active exposure as of August 2026, and covers the affected versions, the two-stage attack chain, the AI coding agent persistence mechanism, the indicators of compromise, the first-hour runbook, and the structural reason both provenance validation and static scanning returned clean verdicts.
What Happened: keyv, cacheable, and flat-cache Were Poisoned at the Source
The keyv supply chain attack began when an attacker compromised the GitHub account of maintainer Jaredwray and pushed malicious code into the tagged source of eleven packages, which the legitimate CI workflow then built, signed, and published to npm. The compromise landed upstream of the build, so npm provenance and SLSA attestation passed cleanly on malicious artifacts.
| Package | Malicious version | Safe version | Approx. monthly downloads |
|---|---|---|---|
keyv | 6.0.0 | 5.6.0 | 604M |
flat-cache | 6.1.24 | 6.1.23 | 580M |
file-entry-cache | 11.1.6 | 11.1.5 | 571M |
cacheable-request | 13.0.20 | 13.0.19 | 137M |
@cacheable/utils | 2.5.1 | 2.5.0 | 34M |
cacheable | 2.5.1 | 2.5.0 | 30M |
@cacheable/memory | 2.2.1 | 2.2.0 | 28M |
cache-manager | 7.2.10 | 7.2.9 | 16M |
@cacheable/node-cache | 3.1.2 | 3.1.1 | 6M |
@cacheable/net | 2.1.1 | 2.1.0 | 3.7K |
ecto | 5.0.1 | 5.0.0 | 4.5K |
The worm spread well past the seed set by abusing publishing credentials stolen from poisoned build runners. Vendor counts disagree, which is expected while a worm is still propagating during analysis: Endor Labs verified 373 packages across 1,006 malicious versions, SafeDep counted 353 poisoned versions across 79 names, Wiz and OX Security reported 400 to 440 or more packages, and Aikido's upper estimate reached 868 packages across 1,381 versions. Treat the blast radius as several hundred packages and rising rather than a settled number, with combined reach above 2 billion monthly installs per OX Security. Organizational npm scopes caught in the secondary wave include @servicetitan (roughly 90 packages), @or-sdk (roughly 90), @onereach (roughly 80), @ornikar (roughly 40), @qlik (roughly 25), plus @nebula.js, @hubsync, and Picsart packages.
Disclosure timestamps also vary. Endor Labs and Snyk place the first malicious publish at roughly 09:30 to 09:35 UTC on August 4, 2026, while Socket's timeline puts [email protected] at 14:05 UTC with the cacheable family following between 15:39 and 15:44 UTC. The source repository showed force pushes, tag deletions, and a commit message reading add setup.mjs and Math_Symbol.js to all @keyv/* packages in the same window. Primary sources here are Endor Labs, Snyk, Socket, Wiz, Aikido, JFrog, SafeDep, and OX Security.
How the Attack Chain Executes: preinstall, Bun 1.3.13, and a 728 KB Payload
The keyv supply chain attack executes in two stages. A preinstall lifecycle hook runs a small obfuscated loader that downloads the legitimate Bun runtime, and Bun then executes a 727,680-byte obfuscated bundle that harvests credentials and republishes itself into other packages.
Stage One: The preinstall Hook and the Bun Loader
The trojanized package.json adds a single line: "preinstall": "node setup.mjs". The loader setup.mjs is 29,918 bytes, lightly obfuscated, and detects Linux, macOS, and Windows. When Bun is absent, the loader downloads an unmodified Bun 1.3.13 binary from the official release path github.com/oven-sh/bun/releases/download/bun-v1.3.13/, extracting it with system unzip, PowerShell Expand-Archive, or a bundled JavaScript ZIP parser fallback, and leaving temporary directories matching bun-dl-*. Pulling a signed, widely trusted runtime from an official GitHub release means the loader stage needs no attacker-controlled domain, and no binary reputation check will flag the download.
Stage Two: The Math_Symbol.js Payload
Bun executes the second stage, delivered as Math_Symbol.js or math_init.js, both 727,680 bytes and byte-identical. A third artifact, router_runtime.js, has also been observed. Socket documents the bundle as a Bun build using basE91-encoded strings with polymorphic per-scope alphabets, which defeats naive string-matching signatures, and recovered four module tags that map to the malware's architecture: [collector] for credential harvesting, [dispatcher] for exfiltration routing, [provenance] for attestation handling, and [publish] for autonomous republication.
Stage Three: Credential Harvesting
The collector sweeps far wider than npm credentials, which is what sets the rotation scope in the runbook below.
- Cloud:
~/.aws/credentials, environment variables, the instance metadata service at169.254.169.254/latest/api/token, IAM security-credentials endpoints, AWS Secrets Manager enumeration, GCP service account keys, and Azure client secrets. - Kubernetes and Vault:
/var/run/secrets/kubernetes.io/serviceaccount/token,KUBECONFIGfiles,VAULT_TOKEN,~/.vault-token, and/home/runner/.vault-token. - npm:
~/.npmrcauthToken values, validated live againstregistry.npmjs.org/-/whoami. - GitHub:
ghp_personal access tokens,gho_OAuth tokens,ghs_Actions and App tokens, andACTIONS_ID_TOKENOIDC values recovered by dumping GitHub Actions runner memory. - Third-party and generic: Stripe
sk_andpk_keys, Slackxoxtokens, and a TruffleHog-style regex sweep across roughly 200 glob patterns covering.envfiles, PEM and SSH keys, Terraform state,docker/config.json, and IDE config files.
Stage Four: Exfiltration and Self-Propagation
The dispatcher creates public GitHub repositories under the victim's stolen identity via POST /user/repos and commits stolen data through GraphQL createCommitOnBranch mutations. The payload RSA-encrypts the data first, so only the attacker holds the key. Every exfiltration repository carries the description string Shai-Hulud: Here We Go Again, and counts range from 821 (OX Security) to roughly 1,300 (Aikido) depending on when the count was taken. The fallback channel is npm-cache[.]com:443/router, a domain registered 2026-05-22 and resolving to 104.21.35[.]216 behind Cloudflare, alongside a DomainSender component performing DNS tunneling. Wiz additionally reports an Ethereum smart contract used to retrieve the C2 domain, which remains single-source at the time of writing.
Propagation runs off the stolen publishing credentials. The payload queries registry.npmjs.org/-/v1/search?text=maintainer: to enumerate every package a stolen token can publish, downloads each tarball, injects the identical preinstall hook and payload files, recomputes the integrity and shasum fields so the tarball still validates, bumps the version, and republishes through registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/. That last path is npm OIDC trusted publishing, used as a propagation channel rather than defeated.
The Persistence Twist: Claude Code and VS Code Hooks Run Without npm Install
The keyv wave ships persistence through AI coding agent and IDE configuration inside the seed packages themselves, which means a developer can be compromised by opening a repository folder without ever running npm install. Earlier Shai-Hulud waves targeted Claude Code environments and VS Code persistence in some variants. This wave makes those hooks a primary execution path rather than a variant behavior. Two project-local files carry the loader: .claude/settings.json registers a SessionStart hook, and .vscode/tasks.json registers a task with "runOn": "folderOpen".
| Config file | Trigger | Effect |
|---|---|---|
.claude/settings.json | SessionStart hook | Runs the loader when a Claude Code session starts in the project |
.vscode/tasks.json | "runOn": "folderOpen" task named Environment Setup | Runs the loader when a developer opens the project folder |
Snyk traced the Claude hook addition to commit d8c850c7. Researchers describe the file paths differently: Snyk reports each config invoking the setup.mjs in its own directory, while Aikido reports the two crossed. Hunt for setup.mjs in both directories rather than assuming one layout. A separate mechanism named gh-token-monitor installs as gh-token-monitor.sh, a systemd unit gh-token-monitor.service on Linux, and a launchd plist com.user.gh-token-monitor.plist on macOS, and survives package removal.
The operational point lands harder than the novelty. Dependency scanning reads package.json and the lockfile. Dependency scanning does not read .claude/settings.json or .vscode/tasks.json, because those files have never been part of the software supply chain. They are now.
Why npm Provenance and SLSA Attestation Did Not Stop This
The malicious [email protected] carried valid npm provenance signed by GitHub Actions, and the attestation was entirely accurate. The attacker compromised the source repository, so the legitimate workflow faithfully built and attested a malicious artifact.
Provenance proves where an artifact was built and that nothing tampered with the artifact afterward. Provenance proves nothing about whether the source that entered the build was trustworthy. That distinction is the whole incident, and this is the second consecutive Shai-Hulud wave to inherit trusted publishing infrastructure rather than defeat that infrastructure.
One mitigating fact is worth stating plainly. Per JFrog, npm 12 and newer do not execute preinstall hooks by default, so environments already on npm 12 or later were not infected through the install path. Those environments remained exposed through the IDE and agent hooks above.
Indicators of Compromise (IOCs) and Behavioral Signals
Hunt in three places, in this order: the malicious package versions in your lockfiles, the payload file hashes on disk, and Bun process execution on machines that do not use Bun. The last of those is the highest-signal indicator on a CI runner, since a caching library has no reason to introduce a second JavaScript runtime.
File hashes:
| Artifact | SHA-256 |
|---|---|
setup.mjs (wave one) | 54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668 |
setup.mjs (later waves) | fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb |
Math_Symbol.js and math_init.js (727,680 bytes) | 9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc |
File system, network, and CI indicators:
| Category | What to hunt for |
|---|---|
| Package versions | The eleven seed versions above, blocked by exact version rather than by name, since a safe version of every affected package exists |
| Dropped files | setup.mjs, Math_Symbol.js, math_init.js, router_runtime.js anywhere under node_modules, $HOME, or /tmp |
| Manifest hook | Any package.json where scripts.preinstall equals node setup.mjs |
| Persistence artifacts | gh-token-monitor.sh, gh-token-monitor.service, com.user.gh-token-monitor.plist; .claude/settings.json with a SessionStart hook; .vscode/tasks.json with a folderOpen task |
| Runtime artifacts | bun-dl-* temporary directories, especially under /tmp; the process chain node setup.mjs spawning a downloaded bun binary |
| Network | npm-cache[.]com (104.21.35[.]216) port 443, path /router; the Bun/1.3.13 user-agent from hosts with no Bun dependency; requests to 169.254.169.254 from build agents; Bun release downloads on hosts that do not use Bun |
| Git, GitHub, and CI | New public repositories under your org with the description Shai-Hulud: Here We Go Again; unexpected POST /user/repos calls from CI identities; npm publish events nobody initiated, especially patch bumps; anomalous OIDC exchange against registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/ |
One string in the payload deserves a direct answer. The bundle embeds IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients, an extortion line intended to discourage token revocation. The line is new to this wave and carries no technical weight. Revoke the token.
Immediate Response: The First-Hour Runbook
Work in this order: stop the bleeding in CI, determine exposure from lockfiles, rotate credentials starting with npm and GitHub publishing tokens, then hunt persistence on developer endpoints. Rotation comes before forensics here, since the worm's next hop is your publishing token.
- Freeze publishing. Revoke every npm automation token and disable trusted publishing for affected repositories before anything else, since any token a poisoned runner could reach is the next propagation vector.
- Enumerate exposure. Run
npm ls keyv flat-cache file-entry-cache cacheable-request cacheable @cacheable/utils cache-manager @cacheable/net @cacheable/node-cache @cacheable/memory ecto --all, then grep lockfiles and CI logs for the malicious versions listed above. - Pin and rebuild without executing hooks. Add an
overridesblock inpackage.jsonmapping all eleven packages to their safe versions, then runnpm install --package-lock-only --ignore-scripts, removenode_modules, runnpm cache clean --force, and finish withnpm ci --ignore-scripts. - Rotate every credential the affected runner or workstation could reach: GitHub personal access tokens and App tokens, cloud keys and assumed roles, Kubernetes service account tokens, Vault tokens, Stripe and Slack keys, database connection strings, and SSH keys. The regex sweep found anything sitting on disk, so scope rotation accordingly.
- Audit Git and GitHub activity. Search your org for repositories carrying the
Shai-Hulud: Here We Go Againdescription, review force pushes and tag deletions on affected repositories, and check for package publishes nobody initiated. - Hunt persistence on endpoints. Search developer machines and runners for
setup.mjs,Math_Symbol.js,gh-token-monitor.*, and modified.claude/settings.jsonor.vscode/tasks.jsonfiles. - Rebuild rather than clean. Treat any CI runner or developer machine that executed the payload as fully compromised and rebuild the host instead of remediating in place.
Finding the Package Is Not the Same as Knowing the Package Ran
Every static gate in the pipeline returned a clean verdict on [email protected]. The signature was valid, the provenance was valid, the SLSA attestation was valid, and the package carried no known CVE. What was anomalous was never the artifact. What was anomalous was the behavior: a caching library spawning a downloaded runtime, reading the cloud metadata service, and opening an outbound channel.
Follow the failure through each control. A malicious package with no CVE is invisible to a scanner matching known-bad identifiers. A polymorphic basE91-encoded bundle is invisible to string-based signatures. A payload delivered by an officially signed Bun binary is invisible to binary reputation. The only unambiguously anomalous stage is execution, which is exactly the stage manifest analysis cannot observe.
Kodem's runtime-powered SCA resolves the affected packages across direct and transitive trees, then narrows the list to the dependencies that actually load and execute in your application, which is what makes a several-hundred-package blast radius triageable rather than paralyzing. Application Detection and Response (ADR) catches the runtime stage without a signature: a preinstall script spawning a network download, an unexpected runtime binary executing from /tmp, a build process reading 169.254.169.254, and a caching dependency initiating outbound traffic to a domain registered ten weeks earlier. That distinction between a package being present and a package having executed is the subject of proving vulnerable code was executed in production. For the prior wave in this campaign family, see the Kodem Mini Shai-Hulud PyTorch Lightning and intercom-client breakdown from April 30, 2026.
The Shai-Hulud Lineage: What Is Actually New in This Wave
This is the sixth major Shai-Hulud wave since September 2025, and most of the tradecraft is inherited from the April 2026 Mini Shai-Hulud campaign, which used identical hook filenames and downloaded the same pinned Bun 1.3.13 runtime against PyPI. Three things distinguish this wave: agent and IDE hook persistence shipped in the seed packages as a primary execution path rather than a variant behavior, an extortion string pressuring victims against token revocation, and npm OIDC trusted publishing used as the propagation channel.
The lineage runs from the original September 2025 npm wave through Shai-Hulud 2.0, The Golden Path, the SAP package compromise, the Miasma variant, and Mini Shai-Hulud against PyTorch Lightning and intercom-client. The cross-ecosystem overlap with that April campaign is the strongest behavioral signal available: identical setup.mjs and payload naming, and the same pinned Bun version, across PyPI in April and npm in August.
The attribution gaps are worth stating rather than papering over. No named threat actor has been identified for this wave, the initial access vector into the maintainer's GitHub account is unknown, whether one publishing account or several were compromised remains unclear, and no CVE has been assigned to the incident.
Hardening Your Pipeline Against the Next Wave
Assume another wave lands within 90 days, since five have landed in eleven months. Every control that would have blunted this one is a configuration change available today, and none of them require knowing the next campaign's indicators.
- Disable lifecycle scripts by default in CI. Set
ignore-scripts=truein the build agents'.npmrcand allowlist the small number of packages that genuinely need native compilation. - Upgrade to npm 12 or newer, where
preinstallhooks do not execute by default, which removes the primary install-time vector outright. - Treat
.claude/settings.jsonand.vscode/tasks.jsonas executable code. Add both to code review requirements, diff them in CI, and disable automatic task execution on folder open in managed developer images. - Move publishing to short-lived OIDC with environment protection rules, since long-lived npm automation tokens are the fuel this worm runs on. Require manual approval on the publish environment so a stolen token alone cannot republish.
- Egress-filter your build agents. A runner that can reach arbitrary GitHub release URLs and arbitrary domains can bootstrap a second-stage payload, so allowlist your registry and artifact stores.
- Block the cloud metadata service from build containers. The
169.254.169.254call is one of the highest-value credential sources in the payload and one of the easiest to deny. - Instrument dependency behavior at runtime, not only at scan time, since the malicious and the safe version of
keyvare indistinguishable statically and obviously different at execution. Pair that with the open source security workflow your team already runs on dependency findings.
What This Wave Signals About the Next One
The keyv supply chain attack marks the point where targeting the developer's tooling moved from an occasional variant behavior to a primary execution path. The .claude/settings.json hook is the tell, and that mechanism is the one most likely to be copied.
- Attackers have moved from defeating trust infrastructure to inheriting that trust. Provenance, SLSA, and OIDC trusted publishing all worked as designed and all signed malicious artifacts.
- The AI coding agent is now a supply chain surface. Agent configuration files execute code, they live in repositories, and no dependency scanner reads them.
- The economics favor the worm. One compromised maintainer account reached hundreds of packages and billions of monthly installs within hours.
One falsifiable prediction: the next wave targets agent configuration formats beyond Claude Code and VS Code, specifically MCP server definitions and editor-level agent rule files, because those execute with the same trust and receive less review than package.json.
Frequently Asked Questions
On August 4, 2026, an attacker compromised the maintainer account behind the keyv and cacheable npm namespaces and published eleven malicious package versions containing a Shai-Hulud credential-stealing worm. The worm then used stolen npm publishing tokens to spread into several hundred additional packages across at least nine organizations.
The confirmed malicious seed versions are [email protected], [email protected], [email protected], [email protected], [email protected], @cacheable/[email protected], @cacheable/[email protected], [email protected], @cacheable/[email protected], @cacheable/[email protected], and [email protected]. Roll each one back to the immediately preceding version listed in the affected-packages table above.
Yes. The exfiltration repositories carry the description string Shai-Hulud: Here We Go Again, and the payload shares hook filenames and the pinned Bun 1.3.13 runtime with the April 2026 Mini Shai-Hulud campaign against PyPI. No named threat actor has been publicly identified for this wave.
Grep your lockfiles and CI logs for the malicious versions, search for setup.mjs and Math_Symbol.js on disk, and look for Bun process execution or bun-dl-* directories on hosts that do not use Bun. Also check whether any repository under your org was created with the Shai-Hulud: Here We Go Again description.
AWS, GCP, and Azure credentials including instance metadata, Kubernetes service account tokens, HashiCorp Vault tokens, npm tokens, GitHub personal access tokens and Actions OIDC tokens harvested from runner memory, Stripe and Slack keys, and any secret matched by a TruffleHog-style regex sweep across roughly 200 file patterns. Rotate all of them.
Yes. The campaign plants a SessionStart hook in .claude/settings.json and a folderOpen task in .vscode/tasks.json, either of which executes the loader when a developer or AI coding agent opens the project directory.
Revoke npm automation tokens and disable trusted publishing for affected repositories immediately, ahead of triage. Every stolen publishing token is a propagation hop, and the extortion string in the payload warning against revocation carries no technical weight.
References
- Aikido Security. August 2026. Keyv and friends compromised in npm supply chain attack. Aikido.
- Endor Labs. August 4, 2026. npm malware compromises keyv and cacheable with 500M weekly downloads and spreads to hundreds of packages. Endor Labs.
- JFrog Security Research. August 2026. Major Shai-Hulud campaign strikes npm again, affecting keyv and 400+ packages. JFrog.
- Kodem Security. April 30, 2026. Mini Shai-Hulud Strikes PyTorch Lightning and Intercom Client: Inside the Cross-Ecosystem Supply Chain Attack. Kodem Security.
- OX Security. August 2026. A Massive Shai-Hulud Campaign Hits npm: 440+ Packages Compromised, Over 2B Monthly Downloads. OX Security.
- SafeDep. August 2026. npm Worm Poisons 400+ Packages Across Nine Organisations. SafeDep.
- Snyk. August 2026. Inside the keyv npm compromise: preinstall malware, trusted provenance, and IDE hooks. Snyk.
- Socket. August 2026. Popular npm packages in the keyv and cacheable namespaces compromised in active supply chain attack. Socket.
- The Hacker News. August 2026. Keyv-Linked npm Worm Poisons Hundreds of Packages, Plants Claude Code and VS Code Hooks. The Hacker News.
- Wiz. August 2026. keyv and cacheable npm package hijacked in supply chain attack. Wiz.
Related blogs

Reachability Predicts. Runtime Proves.
Reachability tells you code can execute. Runtime tells you it did.
7

When the AI Edits Its Own Trust Boundary: Remote Code Execution Vulnerability in AWS's Agentic IDE
We found a vulnerability in Kiro, AWS's agentic IDE, that breaks this promise. By planting hidden instructions in a web page Kiro reads, an attacker can make Kiro rewrite its own MCP (Model Context Protocol) server configuration file and gain arbitrary code execution on the developer's machine.
14
Stop the waste.
Protect your environment with Kodem.
A Primer on Runtime Intelligence
See how Kodem's cutting-edge sensor technology revolutionizes application monitoring at the kernel level.
Platform Overview Video
Watch our short platform overview video to see how Kodem discovers real security risks in your code at runtime.
The State of the Application Security Workflow
This report aims to equip readers with actionable insights that can help future-proof their security programs. Kodem, the publisher of this report, purpose built a platform that bridges these gaps by unifying shift-left strategies with runtime monitoring and protection.
.avif)
Get real-time insights across the full stack…code, containers, OS, and memory
Watch how Kodem’s runtime security platform detects and blocks attacks before they cause damage. No guesswork. Just precise, automated protection.

