Worm compromises popular NPM packages

m-novikov1 pts0 comments

Worm compromises hundreds of popular npm packages | Datadog Security Labs

Datadog

Security Research Team

LAST UPDATED<br>August 4, 2026

Key points and observations

On August 4, 2026, a threat actor compromised hundreds of npm packages and used them to propagate a backdoor.

Some of these npm packages have over 150 million weekly downloads.

The injected payload can spread on its own, similar to the Shai-Hulud npm worm.

To check whether you're affected, jump to How to know if you're affected.

What happened

On August 4, 2026, we identified a malicious commit (174f6a5) in the GitHub repository of the popular keyv npm package.

The malicious commit 174f6a5 has since been deleted on GitHub (click to enlarge)

A few minutes later, the same user pushed similar commits to other projects, including jaredwray/cacheable and jaredwray/ecto.

Another malicious commit in the `jaredwray/ecto` repository (click to enlarge)

Timeline

Time<br>Event

August 4, 09:02:37 UTC<br>Unsigned commit 29c773…, adds setup.mjs, Math_Symbol.js , the malicious preinstall, package inclusion, an executing test, and release configuration. This is the earliest recovered malicious state.

09:03:24–09:03:44 UTC<br>The account creates release/v6.0.0-latest-config and opens Keyv pull request #2042. This is the first captured incident-period public event.

09:04:27–09:04:31 UTC<br>Five GitHub-signed github-actions[bot] commits add identical Claude and Visual Studio Code execution hooks and payload blobs to v5, v4, the release lineage, main, and an existing Claude-named branch.

09:08:18 UTC<br>A comment from the account warns that automated commits added unexpected .claude and .vscode hooks and downloader code: “Do not merge or publish until the repository and credentials are investigated.”

09:12:13 UTC<br>The release branch is reset non-fast-forward from 2fd79f… to 29c773…. The reset removes the visible repository hooks but retains the npm loader, second stage, preinstall, and executing test already present in 29c773….

09:13:25–09:13:27 UTC<br>Signed bot commit 5da403… restores the exact hook blobs. Pull request #2043 then closes with the comment “Closing: merging directly to main.”

09:14:58–09:18:28 UTC<br>GH Archive records a bot push to main. Unsigned commit ee2681… then replays the 27-file 29c773… package delta directly onto hook-bearing main, which moves to that commit without a recorded pull request merge.

09:23:50–09:24:37 UTC<br>Commit f97eabc… deletes only the 22-line test that directly invokes the loader, while retaining preinstall, both payloads, and the repository hooks. main moves to f97eabc….

09:29:50–09:35:01 UTC<br>Genuine SLSA provenance for keyv@6.0.0 enters Rekor, binding tag v6.0.0 and f97eabc… to .github/workflows/release.yaml and Actions run 30896232272. The confirmed malicious package publishes at 09:35:00.763.

09:53:17–09:54:12 UTC<br>Signed bot bursts continue on v4, v5, and main. The Keyv v6.0.0 GitHub Release object is created at 09:54:12, after the npm publication. It remains unpublished until 11:28:45.

10:06:08–10:07:17 UTC<br>Unsigned Cacheable commit 893f73f… reuses the exact Keyv payload blobs across ten workspaces. It is pushed to main, and GitHub release setup-files-v1 publishes with a body explicitly describing the two payload files and preinstall.

10:09:44–10:14:41 UTC<br>Nine confirmed malicious Cacheable-family versions publish from 893f73f… through Actions run 30899303541: @cacheable/net@2.1.1, @cacheable/node-cache@3.1.2, cacheable@2.5.1, flat-cache@6.1.24, cacheable-request@13.0.20, @cacheable/memory@2.2.1, file-entry-cache@11.1.6, @cacheable/utils@2.5.1, and cache-manager@7.2.10.

10:25:47–10:28:02 UTC<br>Unsigned Ecto commit 983ce1a… adds the same Keyv payload blobs and preinstall, is pushed to main, and becomes release v5.0.1. Confirmed malicious ecto@5.0.1 publishes at 10:28:01.451 from Actions run 30900695639.

11:28:36–11:28:45 UTC<br>The Keyv v6.0.0 tag is deleted for the fourth observed time, then the GitHub release created at 09:54 publishes almost two hours after the npm artifact. A GitHub ReleaseEvent is therefore not a reliable npm publication marker in this incident.

11:30:02–11:47:55 UTC<br>Independent GH Archive events record further signed github-actions[bot] pushes on v5, v4, and the old Claude-named branch.

Analysis of the Payload

The following chain applies when a compromised package invokes the loader through a life cycle script and the package manager permits dependency life cycle scripts:

Phase<br>Behavior<br>Result

Initial execution<br>npm invokes node setup.mjs through preinstall<br>The loader runs during installation without an application importing the package

Runtime staging<br>The loader accepts an installed bun or downloads Bun v1.3.13<br>The second stage runs under Bun rather than the npm-spawned Node.js process

Backgrounding<br>Outside recognized CI, the second stage respawns itself detached<br>Collection can continue after the parent installation process ends

Collection<br>Files, environment variables, runner memory, and...

commit github cacheable release malicious main

Related Articles