On August 4, 2026, attackers compromised the maintainer account behind keyv — one of the most widely used caching libraries in JavaScript — and within hours a self-propagating worm had spread to more than 400 npm packages representing over two billion monthly installs, the largest npm supply-chain incident on record. But scale is not the scary part. The poisoned packages were published through the victims' own legitimate release pipelines, carrying valid SLSA provenance attestations signed through npm's Sigstore-based trusted publishing. Every signature check passed. Every provenance verification came back green. The malware was, cryptographically speaking, legitimate.
The short version up front: the trust mechanisms the ecosystem spent years building — provenance, signing, OIDC trusted publishing — now attest to malware as faithfully as to clean releases, because attackers moved up one layer and compromised the pipelines those mechanisms vouch for. If your platform runs npm install on code you did not write — and every git-push PaaS does, on every deploy — defenses have to move into the build environment: kill install scripts by default, make builders ephemeral and secret-poor, enforce lockfiles, treat provenance as a lead rather than proof of safety, and rehearse a response sequence that removes persistence before rotating a single credential. The checklist is in the fourth section; the rest of this post is why each line earned its place.
The wave in 60 seconds
The facts, per Sygnia's report on what it calls the largest Shai-Hulud wave to date:
- Seed: the
keyvmaintainer account, plus related packagesflat-cache,file-entry-cache, and thecacheablefamily.keyvalone sees roughly 127 million weekly downloads. - Blast radius: 400+ npm packages (community detection lists count 434 packages and over 1,700 malicious versions) with a combined footprint above two billion monthly installs — reached within hours of the first poisoned publish.
- Entry: the attacker pushed malicious code directly to the victim project's
mainbranch and cut a normal release. No stolen npm token impersonating a maintainer from the outside; the release went out through the front door. - Payload behavior: credential harvesting across a widening surface — npm and GitHub tokens, cloud credential chains, CI tokens, Kubernetes and Vault secrets, SSH keys — including scraping CI runner process memory for values masked in logs. Stolen credentials turn every victim into a distribution node: the worm enumerates every package the victim can publish and pushes infected versions via stolen tokens or OIDC trusted publishing.
- Infrastructure: no hardcoded command-and-control domain to blocklist. The payload resolves its fallback endpoint dynamically by reading an attacker-controlled Ethereum smart contract, so operators rotate infrastructure without touching the malware. Exfiltrated credential bundles, encrypted, get written into attacker-created public GitHub repositories — hiding in ordinary developer traffic.
- Persistence with an AI-agent twist: this wave planted auto-run hooks in
.claude/settings.jsonand.vscode/tasks.json, meaning opening an infected repo in Claude Code or VS Code — not just runningnpm install— could trigger it. The first documented worm to target AI coding-agent configs for persistence.
Sygnia's thesis sentence is the one to memorize: provenance proves which pipeline produced an artifact, not that the pipeline was behaving as intended. Signing and provenance are necessary but no longer sufficient — and August was not the first time the trust layer vouched for malware. It was the third.
What changed in attacker tradecraft: three waves, one direction
Each wave of the Shai-Hulud family moved the point of compromise one step closer to the machinery of trust itself:
| September 2025: the original worm | May 2026: TanStack + AntV | August 2026: keyv | |
|---|---|---|---|
| Scale | 500+ packages, incl. @ctrl/tinycolor and CrowdStrike-maintained packages | TanStack: 84 malicious versions across 42 packages in under 6 minutes; AntV: ~637 versions across 323 packages in a 22-minute burst | 400+ packages, 1,700+ versions, 2B+ monthly installs within hours |
| Compromise point | Phished maintainer credential → stolen npm token publishes poisoned versions | TanStack: pull_request_target misconfiguration + GitHub Actions cache poisoned from a fork PR + OIDC token scraped from runner process memory; AntV: compromised atool maintainer account | Attacker pushes to main and cuts a normal release through the victim's own pipeline |
| Trust-layer status | Malware published with stolen tokens; provenance absent or clearly off | First documented malicious packages with valid SLSA provenance (Snyk) — minted through TanStack's legitimate trusted-publishing identity (CVE-2026-45321, CVSS 9.6) | Valid Sigstore/SLSA provenance on everything, by construction — the genuine pipeline built exactly what was asked of it |
| Payload | postinstall downloads a bundle, runs TruffleHog locally, exfiltrates to webhook.site, seeds public "Shai-Hulud" repos | Same worm lineage, Mini Shai-Hulud branding (TeamPCP); Socket flagged most AntV packages within 6–12 minutes of publication | Broadened harvesting (cloud chains, K8s/Vault, runner memory), Ethereum-contract C2, exfil via public GitHub repos, agent-config persistence |
Read the middle row diagonally and the trend is unmistakable: September stole the keys to the registry, May stole the identity of the pipeline, and August simply became a legitimate release. Each step makes registry-side defenses — signature checks, provenance verification, token hygiene — less sufficient on their own, because each step moves the malicious act further inside the perimeter those defenses assume is clean.
The ecosystem did respond at the layer it controls. On July 8, 2026, npm v12 shipped with install scripts disabled by default — dependency preinstall/install/postinstall scripts no longer run unless explicitly allowed via allowScripts, and Git plus remote-URL dependencies went opt-in too — roughly eighteen months after pnpm made the same move. Almost every worm since late 2025 executed at install time, so closing that path by default is the highest-leverage registry change in npm's history. But note the timing: the default flipped in July, and the keyv wave still landed in August through release pipelines. The registry can disarm the payload's favorite trigger. It cannot verify the intent of a release cut from a compromised main.
Why a git-push PaaS is the blast radius
Here is the uncomfortable mapping. A self-propagating npm worm wants three things: hosts that routinely install untrusted dependency trees, environments rich in high-value credentials, and onward publish paths. A git-push platform's build layer is all three wearing a trench coat.
Walk the concrete attack path. A tenant pushes a commit. Their dependency tree — direct and transitive, pinned or (worse) floating — includes a package poisoned in the current wave. The platform's builder checks out the repo and runs npm install followed by the build command.
If install scripts execute, the worm now runs inside the builder with access to everything the builder holds: the deploy token that pushes the finished image, registry credentials, cloud API keys, and whatever secrets got mounted "temporarily" three years ago. It harvests them, exfiltrates over ordinary HTTPS, and — if any harvested credential has publish rights anywhere — mints the platform's infrastructure as the next distribution node. The tenant's app may build and deploy perfectly. Nothing looks wrong. The build log shows a green checkmark next to a credential-theft event.
Compare that to the same worm on a developer laptop. The laptop holds one developer's credentials; the builder holds the deployment-path credentials for every tenant it serves. Builders are also far more predictable targets: they run the same install-then-build sequence on untrusted input continuously, with no human watching the terminal, and their egress to GitHub, npm, and cloud APIs is structurally indistinguishable from legitimate traffic. Sygnia's standing guidance — any secret reachable from a build environment should be treated as already exposed — was written for CI generally, but it reads like it was written about multi-tenant builders specifically.
This is also where the "just check provenance" advice quietly fails for a PaaS. Provenance verification answers "did the claimed pipeline build this artifact?" A builder consuming a tenant's dependency tree is not verifying the tenant's pipeline; it is executing third-party code at install time. The artifact the worm cares about is not the tarball's signature — it is the ten seconds of arbitrary code execution npm install grants every dependency in the tree. Registry trust answers a question the builder never asked.
The build-layer controls that actually contain it
So what replaces the npm install then docker build default? The controls below are not new — but the keyv wave changes their priority. Each maps to a specific tradecraft step from the August and May waves, and skipping one leaves that step unopposed.
| Control | The TTP it kills | Concrete shape on a build layer |
|---|---|---|
| Disable install scripts by default, allowlist the exceptions | Worm entry via install hooks in direct and transitive deps | Install with --ignore-scripts (or ship npm v12+, where allowScripts defaults to off); approve per-package exceptions for the few that genuinely need postinstall |
| Ephemeral, single-use builders | Persistence and lateral movement in long-lived build hosts | One builder per build, destroyed afterward. Also neuters the agent-config persistence trick — there is no next session to hook |
| Secret minimization + a reachability map | Harvesting of deploy tokens, registry creds, cloud keys, K8s/Vault material | Builders get the minimum credentials that can complete a build; keep a live map of every secret reachable from each build environment and a rehearsed rotation runbook |
| Egress allowlisting | Exfiltration, dynamic C2 resolution, pushes to attacker repos | Builders talk to the registry, the git host, and declared artifact stores — not the open internet. A surprise C2 lookup or public-repo push fails loudly |
| Hermetic, lockfile-enforced installs | Floating ranges resolving to poisoned versions mid-wave | Require a committed lockfile, install with --frozen-lockfile, fail builds without one. Pin the toolchain too — the worm era is a bad time to discover what latest means |
| Build-log and environment hygiene | Credential capture from logs and env dumps | Mask secrets in logs as a backstop, but assume masking fails (May scraped runner memory for masked values); prefer short-lived, build-scoped credentials |
| Provenance as lead, not proof | False confidence from green checks on poisoned artifacts | Keep verifying provenance — its absence is still a strong signal — but treat attestations from unexpected release workflows as investigation triggers, never a verdict of safety |
| Fork-PR and cache isolation | The TanStack path: poisoned cache written from a fork, consumed by a privileged release job | Untrusted builds must never write caches that trusted release workflows consume; restrict which workflows can mint OIDC publish tokens |
| Publish cooldowns and quarantine | Self-propagation bursts (637 versions in 22 minutes) | For any registry you operate: rate-limit publishes per identity, quarantine brand-new versions, alert on publish-velocity anomalies |
Two honest caveats. First, none of this stops a tenant from deploying an app whose runtime code is malicious — that is a different threat model (sandbox the workload, not just the build). These controls stop the tenant's dependencies from turning your builders into the worm's infrastructure. Second, the table costs real engineering: ephemeral builders need fast image caching, egress allowlists need maintenance, and lockfile enforcement will break someone's Friday deploy. Price that against the alternative Sygnia documents — every compromised build environment doubling as a distribution node — and it stops looking optional.
When it happens anyway: the response sequence
Assume the controls narrow the blast radius but a wave still lands a payload in a builder during the window between first publish and detection — Socket's 6–12 minutes on AntV is best-case. Sygnia's response guidance is unusually specific about ordering, and the order is what teams get wrong under pressure:
- Run the propagation-node test first. Establish whether the compromised environment held npm publishing privileges, GitHub write access, or OIDC publishing rights — and whether anything was published, deployed, or released afterward. This single test decides whether you are only a victim or also a distributor, and it sets the scope of everything after it.
- Remove persistence before touching credentials. Clear caches, wipe builders, check for planted hooks (install scripts, CI config, agent-config files). Some variants monitor token validity and trigger destructive actions on revocation — rotating first can both fail to stop re-infection and make things actively worse.
- Rotate credentials in dependency order. Using the reachability map from the table above: build-scoped tokens first, then anything the builders could reach — registry, git host, cloud, cluster. Rehearsed runbook, minutes, not a scope-reconstruction meeting.
- Purge caches and rebuild from verified-clean sources. Package caches, layer caches, Actions caches — anything the worm could have written to during the window. Then rebuild, and verify the rebuilt artifacts against known-good hashes rather than re-resolving the same poisoned ranges.
- Harden the pipeline, not just the dependency list. Close with the controls the 2026 model specifically defeats: fork-PR and cache isolation, OIDC publishing restrictions, install-script suppression, egress allowlisting, cooldowns. A postmortem that ends at "we pinned the bad version" will relive this incident at the next wave.
One detection note: Sygnia stresses behavioral detection over static indicators, because the August wave's Ethereum-resolved C2 defeats domain and IP blocklists by design. Watch for new public repos under your accounts, commits from unexpected identities, and provenance tied to workflows that never published before. Behavior ages slowly; hashes age in hours.
Trust moved up the stack, and your build layer is where it landed
Step back and the 2026 arc reads as a single lesson about where verification has to live. The ecosystem spent the post-SolarWinds years pushing trust down into artifacts: sign the release, attest the provenance, verify at install. That work was worth doing — provenance still catches tampering, and npm v12's script default closes the most abused execution path in the registry's history. But Shai-Hulud's evolution from stolen tokens to stolen pipeline identity to legitimate releases with valid signatures proves the attackers read the same strategy documents defenders did, and moved one layer up. The artifact layer now vouches for malware correctly and uselessly at the same time.
What has not moved is the build environment — the one place where untrusted code still reliably executes with valuable credentials in reach. Every control that mattered in August lived there: whether install scripts ran, what secrets the builder held, where its network could reach, whether its caches crossed a trust boundary, how fast the team could sequence persistence-removal before rotation. None of those is a registry feature. All of them are platform engineering.
For teams running their own build infrastructure, that is almost good news: the decisive layer is the one you fully control. Harden the builders, shrink what they can reach, rehearse the sequence — and the next wave finds a dead end instead of a distribution node.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. If the 2026 worm waves have you rethinking what your build layer is allowed to touch, star the repo on GitHub or deploy your first app today.



