A credential-stealing npm worm that first appeared in keyv@6.0.0 spread beyond the Keyv and Cacheable namespaces into hundreds of packages across multiple organizations on August 4, 2026.
SafeDep verified 353 poisoned versions across 79 package names in the npm registry. Its monitoring put the wider footprint at 442 versions across 353 names, while 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 malicious release used a preinstall script to run a credential-stealing bundle inside developer and continuous integration (CI) environments. SafeDep and Socket say it can harvest repository, package registry, cloud and private-key material, then use available npm publishing access to poison more packages.
The Keyv repository also retained separate Claude Code and Visual Studio Code (VS Code) hooks that can execute the payload once a user trusts the workspace or permits the project configuration.
Socket says any workstation or runner that executed an affected version should be treated as credential-exposed. SafeDep advises responders to remove the malware’s credential-revocation watcher before rotating exposed tokens and keys.
Revocation is the watcher’s trigger; rotating first can run an attacker-supplied local handler. npm 12 blocks unapproved dependency lifecycle scripts by default, but earlier npm clients and other install paths that permit lifecycle scripts remain exposed.
The first confirmed malicious release was keyv@6.0.0. It added node setup.mjs as a preinstall command and included setup.mjs and Math_Symbol.js in the package while leaving the compiled library code unchanged.
Stage one checks for Bun, downloads version 1.3.13 from the runtime’s official GitHub releases if needed, and hands off to a 727,680-byte compiled bundle.
![]() |
| Source: SafeDep |
SafeDep’s payload analysis says the bundle harvests GitHub, npm, cloud, Vault, Kubernetes, database and private-key material. It also reads GitHub Actions runner memory, installs a token-revocation watcher and carries npm publishing machinery. Socket separately decoded code for modifying, versioning and republishing packages available to a stolen npm identity.
The registry changed too quickly to support a fixed campaign-wide list of packages still tagged latest. At 5:40 p.m. India Standard Time (IST) on August 4, npm package pages showed earlier releases restored as latest for at least nine packages from SafeDep’s initial set, including keyv@5.6.0, flat-cache@6.1.23, and cache-manager@7.2.9.
The full campaign could not be independently mapped package by package, so exposure checks must use exact package names, resolved versions and lockfiles rather than a cached list of current tags.
The totals reported by SafeDep and Aikido count malicious package artifacts, not victim systems. They establish campaign scale but do not show how many machines installed or executed the payload. Determining system-level exposure requires the exact dependency version resolved on the machine and whether its lifecycle script ran.
With tags changing, some related packages remaining clean and the full set incomplete, a namespace-level blocklist risks both missing poisoned versions and treating unaffected releases as compromised.
The repository carries a second execution path. Its .claude/settings.json contains a SessionStart hook that calls .vscode/setup.mjs. The .vscode/tasks.json file contains an Environment Setup task with runOn: folderOpen that calls .claude/setup.mjs. Those files create a route for executing the payload from a checked-out repository, but they do not run unconditionally in every default environment.
VS Code blocks automatic tasks in an untrusted workspace and prompts before allowing them by default. Claude Code applies workspace trust to repository-supplied project settings. At 5:40 p.m. IST on August 4, both hook files were still present on main. The core/keyv/package.json manifest still declared version 6.0.0, retained node setup.mjs as its preinstall command, and listed setup.mjs and Math_Symbol.js for publication.
The poisoned Keyv release carried valid OpenID Connect (OIDC) and Supply-chain Levels for Software Artifacts (SLSA) provenance because it passed through the project’s legitimate GitHub Actions release workflow. The attestation correctly identified the build process, but could not establish that the source entering it was safe.
SafeDep found the same payload under direct npm publishing accounts elsewhere in the campaign. It 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. The package burst therefore cannot establish how many accounts were compromised.
The commit that planted the Claude Code and VS Code hooks carried a green GitHub-verified badge and named github-actions[bot] as its author. The verified badge establishes that the commit’s signature was valid, while SafeDep said its author field was set to github-actions[bot]; neither fact identifies who controlled the credential that submitted it.
Together, the provenance and commit records verified the build and signing paths, but not whether the source was safe or who controlled the credential.
Not every package linked to the original maintainer was poisoned. SafeDep says the published @keyv/* adapters and the Keyv 5.x line were clean. The repository is a separate risk: a later commit staged the payload files across all 19 workspace packages, so a release cut from the still-poisoned tree would carry them.
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 places the August activity in the Shai-Hulud family. The overlap supports a malware-family connection. It does not establish who operated the August activity; neither the initial access path nor a named actor is known.
No public incident statement from the maintainer, npm or GitHub was located at the reporting cutoff.



























