On 4 August 2026, whoever controlled the maintainer account behind keyv — a caching library with roughly 127 million weekly downloads — published a version carrying a script that runs on its own when the package is installed. Within about half an hour, the malicious code had copied itself into hundreds of npm packages and jumped to projects across several unrelated organisations. No one had to run anything by hand: installing a dependency was enough.
What sets this case apart from another npm credential theft is where it leaves its persistence. Beyond looting the secrets of the development environment, the worm writes two configuration files into the repository itself — one for Claude Code, one for VS Code — wired to launch its payload the moment a developer opens the project or starts a session with their AI agent. It turns the trust you place in your own project's configuration into an execution mechanism.
At a glance
- The worm, a Mini Shai-Hulud variant, started in keyv@6.0.0 on 4 August and self-propagates using the publishing tokens it steals, in a supply chain attack.
- A
preinstallscript steals npm, GitHub, AWS, Vault, Kubernetes and private-key credentials across development and continuous integration environments. - It plants
.claude/settings.jsonand.vscode/tasks.jsonin the repository, wired to run when the project opens in VS Code or Claude Code. - keyv pulls roughly 127 million weekly downloads; flat-cache and file-entry-cache sit under ESLint, so it reached developers who never chose them.
- The counts still diverge: The Hacker News reports over 400 packages within the first half hour; Aikido puts it at more than 800 packages and over a thousand versions. No CVE assigned.
- Defence: pin versions and lockfiles,
--ignore-scripts, short-lived publishing tokens with OIDC, isolate CI secrets, and review any configuration files that turn up in a checkout.
How it works: three linked pieces
The attack chains three capabilities that had each been seen separately, but that together explain why it spread so fast and why it is hard to root out.
Credential theft at install time
The compromised package carries a preinstall script, an instruction npm runs automatically before the code is even used. That script downloads the Bun runtime and launches an obfuscated harvester that goes after what unlocks everything else: .npmrc tokens, GitHub CLI credentials, AWS keys, Vault tokens, Kubernetes configs, private keys and database connection strings. According to Datadog Security Labs, the payload runs to about 727 KB. The theft happens on the developer's machine or the continuous-integration runner, ending in the exfiltration of those secrets before the project is ever built.
Self-propagation with the stolen tokens
Here is the worm mechanism. Among what it steals are npm publishing tokens, and the code uses them to republish poisoned versions of other packages within the compromised maintainer's reach. Every new victim with publishing rights widens the blast radius, which is why the code jumped to packages from organisations with no connection to keyv. The speed — hundreds of packages in the first half hour — is not human: it is a process that replicates on its own.
Persistence in the IDE and the AI agent
The third piece is the most novel. The worm leaves a .vscode/tasks.json and a .claude/settings.json in the repository, configured to run a command as soon as the project opens in VS Code or a Claude Code session starts. It is a form of persistence that does not live in the operating system but in the source code itself: it survives a machine wipe and travels with the repository to anyone who clones it. It depends on one detail — that the developer trusts the workspace — but that trust click is what daily routine grants without thinking.
The reach: keyv, ESLint and the dependency nobody chose
keyv is not just any package. It sits at the base of countless projects, almost always indirectly. Two of the compromised siblings, flat-cache and file-entry-cache, sit under ESLint, the linting tool half the JavaScript ecosystem runs. The result is that the worm reached developers who never wrote keyv or ESLint in their dependency list: they pulled it in as a dependency of a dependency.
On the exact size the trackers do not yet agree, and that divergence is normal in the first hours of a live incident. The Hacker News counted over 400 packages affected in the first half hour; Aikido put the count at more than 800 packages and over a thousand poisoned versions. Several security teams — SafeDep, Socket and Datadog Security Labs among them — confirmed the compromise of keyv@6.0.0 and sibling packages such as file-entry-cache. There is no CVE assigned, which in practice means you cannot lean on a single identifier to track your exposure: you have to read your dependency tree and your lockfile.
Why the usual controls miss it
Endpoint antivirus and CVE-based vulnerability scanning fall short here, for several reasons. Execution happens at the install stage, before the tests and static analysis many teams treat as their safety net. The runtime it downloads, Bun, is a legitimate, signed tool, not a suspicious binary. And the persistence files are project configuration with valid syntax: to a hurried reviewer, a .vscode/tasks.json is background noise. With no CVE, it also fails to surface on dashboards that only watch identified vulnerabilities.
The common thread is that the attack leans on legitimate trust at every step: you trust the package, the publishing token, the runtime and your own repository's configuration. None of those, on its own, is an anomaly.
Detection: what to look at in the repo and the pipeline
Reliable detection is behavioural and review-based, not signature-based. Four concrete signals you can actually hunt:
- IDE or AI-agent configuration files that appear in a checkout where you did not put them. An unexpected
.claude/settings.jsonor.vscode/tasks.jsondeserves a line-by-line read before you open the project. - New
preinstallorpostinstallscripts in the lockfile diff. A change topackage-lock.jsonthat introduces an install hook is a first-order signal. - Anomalous outbound connections from a developer machine or a CI runner during an
npm install: runtime downloads or calls to domains that project never contacts. - Use of npm publishing tokens or cloud credentials from locations or at times that do not fit legitimate activity.
If any of those appear, the starting point is not ‘a malicious package’ but ‘which credentials have passed through this machine or pipeline, and rotate them now’. It is threat hunting work and a matter for an incident response that treats secret theft as the default hypothesis.
Defence: hardening development and continuous integration
No single control stops this; what limits the damage is a chain of decisions about how your organisation consumes third-party software — the substance of a DevSecOps approach and of third-party risk management. These are the measures that pay off most.
Pin versions and respect the lockfile
Open ranges (^, ~) let a freshly published version enter your build with no one approving it. Pinning exact versions, committing the lockfile and using npm ci instead of npm install in CI stops a malicious release from slipping automatically into the next build.
Turn off install scripts by default
--ignore-scripts stops npm running the preinstall and postinstall hooks, which is where this family of attacks gets in. It has a cost — some legitimate packages rely on those scripts to compile binaries — so the sensible practice is to disable them globally and explicitly allow only the few packages that genuinely need them.
Short-lived publishing tokens and trusted publishing
The worm's fuel is stolen publishing tokens. Replacing long-lived tokens with ephemeral credentials and with OIDC-based publishing (trusted publishing), where the registry trusts the pipeline's identity rather than a stored secret, cuts that chain: there is no persistent token left to steal. Tokens that remain necessary should be granular and least-privilege, like any other non-human identity.
Isolate secrets in continuous integration
A CI runner executing an npm install should not have every key to the kingdom within reach. Secrets scoped to what each job needs, no global environment variables holding production credentials, and separation between the environment that builds and the one that deploys: that way, a theft at the install stage does not hand over the whole cloud.
Review repository configuration as routine
The IDE-hook vector makes reviewing .claude/settings.json, .vscode/tasks.json and equivalent files part of code review, not a curiosity. The workspace-trust prompt — in VS Code and in the AI agent alike — stops being an automatic click: opening someone else's repository without reviewing its configuration is accepting its execution. This is also the territory of AI-agent and MCP security.
The bottom line: the trust this worm exploits
The underlying pattern is not a specific package but the implicit trust that holds modern development together. We assume that installing a dependency is an inert operation, that a publishing token only publishes, that a repository's configuration describes the project rather than attacking it. Each assumption is reasonable on its own, and this worm chains all of them. We saw it, with different mechanics, in the easy-day-js case and in the jscrambler infostealer.
The lesson to keep is that AI coding agents add a new, high-trust execution surface: a file that tells the agent what to do when the project opens is, in practical terms, code that runs with your permissions. That is not a reason to distrust the tool, but to treat its configuration with the same rigour as any script. What remains to be seen over the coming weeks is whether the technique gets copied — open-source worm repositories lower the barrier to entry — and whether registries accelerate towards publishing without stored secrets. In the meantime, the operational conclusion is concrete: every time someone on your team installs a dependency or opens a repository they did not write, something runs with their permissions. It is worth knowing what.
This article describes the incident and its mitigation measures using the public information available on 5 August 2026. The counts of affected packages vary between sources and are still being updated; verify your exposure against your own dependency tree and your registry's advisory before taking operational decisions.
The packages named as compromised are based on public disclosures from several security teams with the information available on the date. Naming a package does not imply a flaw in npm or in the rest of its maintainers' work: the vector was the takeover of accounts and publishing tokens. Figures and attributions may evolve.