Three Hours and Forty-Three Minutes

At 09:35 UTC on August 4, 2026, a poisoned version of keyv was published to the npm registry. By 13:18 UTC — three hours and forty-three minutes later — the worm inside it had spread into packages belonging to twelve unrelated organizations, including @deliveroo, @qlik, @servicetitan, @picsart, @onereach, and @ornikar.

SafeDep, which published the most detailed timeline of the incident, confirmed a footprint of 2,234 poisoned versions across 444 package names. Other vendors, measuring different things at different moments, published materially different numbers: Datadog Security Labs counted 796 packages across 1,092 versions, and Aikido counted 434 packages across 1,381 versions. SafeDep’s own confirmed figure started at 353 versions across 79 names before expanding through the day. The spread between those tallies — three competent research teams, three different answers — is itself the finding: nobody could enumerate the blast radius while it was still growing.

The packages at the center of it are not obscure. keyv, cacheable-request, flat-cache, and file-entry-cache account for roughly 1.9 billion downloads per month between them. They are not dependencies most teams chose — they are dependencies of dependencies of ESLint, of build tooling, of the transitive substrate underneath a typical Node.js install. Almost nobody has them in their package.json. Almost everybody has them in their node_modules.

The most compressed window came between 10:12 and 10:46 UTC: eight organizations compromised in thirty-four minutes. That is the entire duration in which a human-staffed SOC would have had to notice an anomalous package install, decide it mattered, escalate it, and act. Most SOCs do not acknowledge a medium-severity alert in thirty-four minutes.

The Attack Chain

The mechanism was deliberately mundane at the entry point and unusually sophisticated after it.

Each poisoned package manifest gained a single line: "preinstall": "node setup.mjs". The preinstall lifecycle hook runs during npm install, before any of your own code executes, with the full privileges of whatever user or CI runner invoked the install. This is not a vulnerability. It is a documented feature of npm that most organizations have never disabled.

setup.mjs was an 11,017-byte obfuscated loader. Rather than executing its payload under Node.js — where endpoint tooling is most likely to be watching — it checked for the Bun runtime, downloaded Bun v1.3.13 from the official GitHub releases URL if absent, executed the real payload under Bun, and then deleted the runtime it had just fetched. Every network request in that sequence goes to github.com. There is no suspicious domain to block.

The payload itself, a 727,680-byte Bun-compiled bundle named Math_Symbol.js, contained ten separately encrypted modules — AES-256-GCM under PBKDF2 with 200,000 iterations — covering credential harvesting, CI workflow injection, npm republishing, and persistence.

What It Took

The harvesting modules were comprehensive rather than targeted:

It then injected a workflow job that dumps every repository secret via ${{ toJSON(secrets) }} and uploads the result as a build artifact — converting a one-time install into a persistent, self-refreshing secret exfiltration channel that survives cleanup of the original package.

There Was No C2 Server to Block

Conventional network defenses assume an outbound connection to attacker infrastructure. This campaign had none. Exfiltrated data was RSA-4096 envelope-encrypted and pushed into public GitHub repositories — 546 of them created on August 4 alone, each carrying the description “Shai-Hulud: Here We Go Again.” StepSecurity and Upwind additionally documented endpoint resolution through calls to an Ethereum smart contract, meaning the operators could rotate destinations without ever hard-coding a domain the defenders could sinkhole.

To a firewall, a proxy, or a DNS filter, all of this is a developer machine talking to GitHub. Which is what developer machines do.

August 4, 2026 by the Numbers

  • 3h 43m: Total campaign window, 09:35–13:18 UTC, across twelve unrelated organizations.
  • 34 minutes: Time in which eight of those organizations were compromised (10:12–10:46 UTC).
  • 2,234 versions / 444 packages: SafeDep’s confirmed poisoned footprint; other vendors counted differently as the campaign ran.
  • ~1.9 billion monthly downloads: Combined reach of keyv, cacheable-request, flat-cache, and file-entry-cache.
  • 546: Public GitHub dead-drop repositories created in a single day — in place of any traditional C2 infrastructure.
  • 0: Invalid signatures. Every poisoned release carried valid npm provenance.

The New Part: Your Coding Agent Is an Execution Surface

Supply-chain worms are not new. What happened in the keyv repository on August 4 was.

Alongside the npm payload, the attacker committed two files that never appeared in any published npm tarball — they existed only in the Git repository:

Read those two lines again. Each file points into the other tool’s directory — a deliberate cross-reference that makes each one look like configuration belonging to something else, and defeats a reviewer who greps for suspicious paths under the directory they happen to be auditing.

The consequence is that installation was no longer required. A developer who cloned the repository to investigate the incident — the exact behavior security-conscious engineers exhibit when a supply-chain story breaks — and opened it in VS Code or Claude Code triggered the payload on folder open or session start. No npm install. No build. No script they chose to run.

The commit carrying these files was pushed at 09:04:30 UTC with a verified GitHub signature spoofing github-actions[bot], so it rendered in the GitHub UI as automated tooling rather than a human change worth reviewing.

This is the structural shift worth internalizing. For twenty years, the security model for reading untrusted code was: cloning is safe, executing is dangerous. AI coding agents and modern IDEs have quietly dissolved that boundary. Session-start hooks, folder-open tasks, MCP server definitions, and agent configuration files are all executable content that activates on the act of looking. Your developers’ AI agents are now part of your attack surface, and almost no organization has a detection strategy that reflects it.

Provenance Validated. The Package Was Still Poisoned.

The most uncomfortable finding is that the software supply-chain controls the industry has spent five years deploying worked exactly as designed — and stopped nothing.

Because the attacker committed the malicious files to the source repository and let GitHub Actions build and publish the result through the legitimate release workflow, npm displayed valid provenance for every poisoned version. The SLSA attestations were genuine. The signatures verified. A policy gate that required signed, attested packages with verifiable provenance would have waved all 2,234 versions straight through.

Provenance answers the question “did this artifact come from the repository and workflow it claims?” It cannot answer “was that repository trustworthy at the moment of release?” Signed artifacts authenticate origin; they do not authorize intent. An organization that trusts signatures while permitting unrestricted install scripts and handing broad credentials to build runners has secured the chain of custody and left the final, decisive step completely unbounded.

Worse, the payload carried working npm publishing machinery with embedded Fulcio and Rekor clients — meaning a stolen OIDC token lets the operator produce future releases that are equally well-attested.

Rotating Credentials Triggers the Trap

The persistence module deserves its own warning, because it inverts standard incident response.

The worm installed a watcher that polls api.github.com/user every sixty seconds using a stolen GitHub token. When that poll returns a 40x — that is, when a defender revokes the token — the watcher executes an attacker-supplied handler through eval. It installs as a LaunchAgent on macOS (com.user.gh-token-monitor.plist, with RunAtLoad and KeepAlive) or a systemd user service on Linux with lingering enabled, and self-removes after twenty-four hours.

The first instinct of every responder — rotate the credentials immediately — is the trigger condition. Hunt these paths before revoking anything:

An incident response playbook that says “rotate first, investigate second” loses here. Getting the order right requires knowing what the persistence mechanism does — which requires having read the analysis, correlated it to your own telemetry, and understood your exposure. In the first hours of an incident, that is exactly the reasoning that does not happen.

The Alerts Fired. In Six Different Places.

Nothing about this campaign is invisible. In a reasonably instrumented environment, it generates signals across nearly every telemetry source a SOC already ingests:

Every one of those is detectable. Several are already detected by default in common tooling. The failure is not sensing. It is that each signal, considered alone, has an ordinary explanation — and each one lands in a different queue owned by a different team.

The Siloed Investigation Problem

  • EDR alert: “Unsigned binary executed from temp directory on developer endpoint” → endpoint team triages → “developer tooling, dev machines do this constantly”
  • CI pipeline log: “preinstall script executed during dependency install” → platform engineering → “normal npm behavior, not an alert”
  • GitHub audit log: “Workflow file modified” → AppSec triages → “commit signed by github-actions[bot], automated”
  • CloudTrail: “Access key used from unfamiliar ASN” → cloud security triages → “CI runners move around, within tolerance”
  • Network telemetry: “Periodic outbound to api.github.com” → network team → “GitHub is on the allowlist”
  • Combined: A self-propagating worm harvesting cloud, registry, and Kubernetes credentials from your build infrastructure, with a booby-trapped persistence mechanism waiting for you to rotate them.

The developer endpoint is where this gap is widest. Dev machines are the noisiest endpoints in any enterprise — they compile unsigned binaries, download toolchains, spawn subprocesses, and hold credentials to production. Most SOCs have long since tuned their developer-endpoint detections down to near-silence, because the alternative was a queue no one could clear. That tuning decision, made years ago for entirely rational reasons, is precisely the condition this worm was built to exploit.

Speed Is the Whole Problem

Reconstruct the required response and the arithmetic becomes clear. Within a window of roughly three and a half hours, a defender needed to: notice a preinstall execution among thousands of routine build events; connect it to a Bun download and a /proc memory read; pivot to cloud audit logs and identify which credentials were exposed; enumerate every lockfile that pinned a poisoned version while it was live; determine whether any developer had cloned the repository and opened it in an IDE or agent; locate the dead-man’s switch on affected hosts; and only then begin rotating credentials — in the right order.

That is a multi-day investigation compressed into a window measured in hours. It spans endpoint, CI, source control, cloud, identity, and network telemetry. No single analyst holds context across all six domains, and no single queue contains all six alerts.

This is the same structural failure that produced the breaches we have written about repeatedly — fourteen ShinyHunters mega-breaches, five-year Typhoon dwell times — only with the clock compressed from years to hours. The attackers automated propagation. The defense is still manual.

What to Do Now

Independent of any platform decision, the concrete actions from this incident are:

Closing the Gap

This is what Intruex automates. AI agents investigate every alert by correlating across endpoint, CI, source control, cloud, identity, and network telemetry — as the primary investigation workflow, not a second-pass enrichment step.

When an EDR alert fires for an unsigned binary executing on a developer endpoint, the investigation does not stop at “developer machines do this.” It checks whether a package install preceded it and which dependency versions were resolved. It correlates against cloud audit logs for credential use from that host in the following minutes. It checks source control for workflow file modifications and newly added agent or IDE hook files. It looks for the periodic outbound polling cadence that indicates a persistence watcher. It assembles a single timeline spanning every telemetry source the SOC ingests — and it does so for every alert, including the low-severity ones on the noisy developer endpoints nobody has time to look at.

Thirty-four minutes is not enough time for a human to do that across six systems. It is enough time for an agent that starts the moment the first alert lands.

The Core Question

If one of your engineers ran npm install at 10:20 UTC on August 4 — pulling a transitive dependency they have never heard of, executing a preinstall script, downloading a runtime, reading your CI runner’s memory, and shipping your cloud and registry credentials to a public GitHub repository — generating separate alerts in your EDR, CI logs, GitHub audit trail, cloud audit logs, and network telemetry, how long before someone connected those five signals into a single compromise narrative? The worm needed three hours and forty-three minutes. If your answer is longer than that, the campaign finished before your investigation started.

Sources: SafeDep incident analysis and timeline (keyv npm supply chain compromise, August 2026), Datadog Security Labs (worm technical analysis and detection guidance), StepSecurity “ChainDrop” analysis and Upwind (Ethereum dead-drop C2 resolution), The Hacker News reporting (August 4, 2026), Socket and Aikido Security telemetry. Figures for campaign scope varied by vendor while the campaign was ongoing; counts cited are as published by each vendor at time of writing.