Skip to main content
Emerging ThreatsMalware & Ransomware

Npm Worm Exploits Hundreds of Packages via Keyv Link

Cluttered software development workspace with laptop, papers, and cables.

"SafeDep verified 353 poisoned versions across 79 package names in the npm registry."

Keyv@6.0.0 and the preinstall vector

The first confirmed malicious release in the campaign was keyv@6.0.0. That release added a preinstall lifecycle command — node setup.mjs — and published two additional files, setup.mjs and Math_Symbol.js, while leaving the compiled library code unchanged. Stage one of the package checks for the Bun runtime, downloads Bun version 1.3.13 from the runtime's official GitHub releases if needed, and then hands execution to a compiled bundle of 727,680 bytes.

SafeDep's analysis says that compiled payload can harvest credentials and secrets from GitHub, npm, cloud providers, HashiCorp Vault, Kubernetes, databases and private keys. It also reads GitHub Actions runner memory, installs a token-revocation watcher and includes npm publishing machinery that can push malicious packages under a stolen identity. Socket reported complementary decoding of code that modifies, versions and republishes packages available to a stolen npm identity.

Workspace hooks: Claude Code and Visual Studio Code

In addition to the preinstall lifecycle script, the repository carried repository-supplied workspace hooks for Claude Code and Visual Studio Code that can execute the same payload after a developer checks out the source and trusts the workspace. The repository’s .claude/settings.json contained a SessionStart hook that calls .vscode/setup.mjs; .vscode/tasks.json included an Environment Setup task with runOn: folderOpen that calls .claude/setup.mjs.

Those hooks do not run unconditionally in every default environment: VS Code blocks automatic tasks in an untrusted workspace and prompts before allowing them, and Claude Code applies workspace trust to project settings. At 5:40 p.m. India Standard Time on August 4 both hook files remained present on main, and the core/keyv/package.json still declared version 6.0.0 with node setup.mjs listed as preinstall and setup.mjs and Math_Symbol.js included for publication.

Scale, shifting tags, and what the totals mean

SafeDep verified 353 poisoned versions across 79 package names; its broader monitoring put the footprint at 442 versions across 353 names. Aikido later reported at least 868 packages across 1,381 versions. Neither broader total was independently reproducible from a complete public list at the reporting cutoff. The vendors emphasized that these counts measure malicious package artifacts in the registry, not how many machines downloaded or executed the payload.

Registry behavior complicated incident mapping. At 5:40 p.m. IST on August 4, npm package pages showed earlier releases restored as latest for at least nine affected packages from SafeDep's initial set, including keyv@5.6.0, flat-cache@6.1.23 and cache-manager@7.2.9. The registry changed too quickly to support a fixed campaign-wide list of packages still tagged latest; SafeDep and Socket warned that exposure checks must look at exact package names, resolved versions and lockfiles rather than relying on cached tag lists.

Propagation mechanics and provenance paradox

The poisoned Keyv release carried valid OIDC and SLSA provenance because it passed through the project's legitimate GitHub Actions release workflow. The attestation correctly identified the build process and a commit that planted the workspace hooks carried a green GitHub-verified badge with the author field set to github-actions[bot]. SafeDep emphasized that the verified badge and author string validate the signature and commit metadata but do not identify who controlled the credential that submitted it.

SafeDep found the same payload under direct npm publishing accounts elsewhere in the campaign and said automated propagation best fits the timing and whole-namespace publishing bursts, although the initiating function was not recovered. The evidence does not show whether the worm spread solely from one stolen publishing identity or also involved separately compromised publisher credentials; therefore the package burst cannot establish how many accounts were compromised.

What this means for developers, CI teams, and maintainers

  • Developers and workstation owners: Any workstation or runner that executed an affected version should be treated as credential-exposed, Socket said. Because the malware installs a token-revocation watcher whose trigger is revocation, SafeDep advises responders to remove the watcher before rotating exposed tokens and keys; rotating credentials first can invoke an attacker-supplied local handler.
  • CI and release engineers: npm 12 blocks unapproved dependency lifecycle scripts by default, but earlier npm clients and alternative install paths that permit lifecycle scripts remain exposed. Determining system-level exposure requires the exact dependency version resolved on the machine and whether its lifecycle script ran — the registry artifact counts alone do not show machine compromise.
  • Package maintainers and repository custodians: Not every package tied to the original maintainer was poisoned — SafeDep noted published @keyv/* adapters and the Keyv 5.x line were clean — but a later commit staged the payload files across all 19 workspace packages, meaning a release cut from the still-poisoned tree would carry the files.

Semgrep documented the same Claude Code and VS Code hooks, setup.mjs filename and Bun 1.3.13 download in an April compromise of the lightning PyPI package; Aikido placed the August activity in the Shai-Hulud family. That overlap supports a malware-family connection but does not identify who operated the August activity. At the reporting cutoff there was no public incident statement from the maintainer, npm or GitHub.

Original reporting: The Hacker News — Keyv-Linked npm Worm Poisons Hundreds of Packages, Plants Claude Code and VS Code Hooks