← Back to the cybersecurity blog

The self-replicating npm worm that plants Claude Code and VS Code hooks

By Thilina Manana · COO y Director Técnico de Seguridad hard2bit · Published: 05 August 2026 · Updated: 05 August 2026
The self-replicating npm worm that plants Claude Code and VS Code hooks AI-generated image

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 preinstall script steals npm, GitHub, AWS, Vault, Kubernetes and private-key credentials across development and continuous integration environments.
  • It plants .claude/settings.json and .vscode/tasks.json in 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.json or .vscode/tasks.json deserves a line-by-line read before you open the project.
  • New preinstall or postinstall scripts in the lockfile diff. A change to package-lock.json that 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.

Frequently asked questions

What is the keyv worm and why does it matter?

It is a credential-stealing worm, a Mini Shai-Hulud variant, that started on 4 August 2026 in keyv@6.0.0, a caching library with roughly 127 million weekly downloads. It self-propagates across npm using the publishing tokens it steals, and hit hundreds of packages in its first half hour, including some that sit under ESLint.

How does it spread with no one running it?

The compromised package carries a preinstall script that npm runs automatically on install. That script steals credentials, among them npm publishing tokens, which the code reuses to publish poisoned versions of other packages. That is how it replicates on its own and jumps to unrelated projects.

What do Claude Code and VS Code have to do with it?

The worm writes a .claude/settings.json and a .vscode/tasks.json into the repository, configured to run a command as soon as the project opens in VS Code or a Claude Code session starts. It is persistence that lives in the source code and travels with the repository, and it depends on the developer trusting the workspace.

How many packages have been affected?

The figures still diverge between sources because the incident is live. The Hacker News counted over 400 packages in the first half hour; Aikido put the count at more than 800 packages and over a thousand poisoned versions. There is no CVE assigned, so it is best to measure exposure against your own dependency tree.

What is --ignore-scripts and why does it help?

It is an npm option that stops the install hooks (preinstall and postinstall) from running, and those hooks are the entry point for this family of attacks. Because some legitimate packages need them to compile binaries, the sensible practice is to disable them globally and explicitly allow only the ones that genuinely require them.

How do I reduce the risk of publishing tokens being stolen?

By 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. Tokens that remain necessary should be granular and least-privilege, and CI secrets should be isolated per job.

If I installed an affected version, is updating enough?

No. If the script ran, you should assume the credentials present on that machine or pipeline are compromised: npm and GitHub tokens, cloud keys, CI secrets. The priority is to rotate them, review the dependency tree and lockfile, and hunt for IDE configuration files you did not add.

Has a CVE been assigned to this incident?

As of publication there is no CVE assigned. It is a compromise of maintainer accounts and publishing tokens rather than a vulnerability in a specific product, which makes it hard to track exposure with a single identifier and forces you to look directly at your dependencies.