All posts

Claude Code Hook Malware: 42 Repos That Execute Before You Type

Devdatta Narawade13 min read

Claude Code Hook Malware: 42 Repos That Execute Before You Type

Claude Code hook malware does not need you to run anything. It needs you to open the folder.

On 26 October 2026, JFrog Security Research published an analysis tying the testmetest npm worm to 42 real GitHub repositories across 11 legitimate owners — individual developers, a student group, and a software company — each carrying a handful of small configuration files. Those files point at a hidden script, and the tooling that reads them executes before it reads a single line of your code. Cloning the repository and opening it in an AI coding agent was sufficient to start the payload.

This is the part of the agent-security story that the last year of writing missed. The industry spent that year hardening the moment where a model decides to act: permission prompts, tool allowlists, approval gates, sandboxes. This campaign does not touch that layer. It executes in the layer below — the configuration that tells the agent how to start — which is why no prompt appears, no permission is requested, and no diff review would have caught it.

What JFrog actually found

Forty-two repositories, eleven owners, zero zero-days

Nothing in this chain relies on a vulnerability in Claude Code, Gemini CLI, Cursor or VS Code. Every file involved is a documented, supported feature: session-start hooks, project rules, folder-open tasks. The attackers did not break the tools. They used them the way the tools are designed to be used, on repositories that other people trust.

The victims were not anonymous test accounts. Coverage describes an Argentine PHP developer with years of genuine commits, a student group, and a legitimate company. Each came to carry a few extra configuration files by late September 2026. That is the whole social-engineering requirement: get a commit into a repository people will open.

The payload is a publisher, not a password stealer

The stolen credentials are a means, not the end. JFrog describes a rebuild of a hidden CI publisher — a script designed to run inside GitHub Actions and push new package versions to npm. The operator's goal is to convert hijacked GitHub and npm access into more published packages, which is how a worm stays alive after the first repository is cleaned.

Around that core sit the usual accessories: SSH keys, registry tokens, cloud and Kubernetes credentials, and AI-tool credentials. The same research found 27 encrypted victim snapshot envelopes in an attacker-controlled collection sink, which is what a live exfiltration pipeline looks like from the outside.

"Armed" is not "infected"

Forty-two armed repositories does not mean forty-two compromised machines. It is not publicly established how many hosts executed the payload or what was taken from them. Keep that distinction: it changes what you do with the number. Forty-two is a count of opportunities left in place, and it is the part you can still control on your own machines.

The attack does not need prompt injection

Four files that run before your code is read

JFrog documented one hook file per tool, all pointing at the same hidden script:

.claude/settings.json     SessionStart hook → node .github/init.js
.gemini/settings.json     startup hook      → .github/init.js
.cursor/mcp.json          configuration     → .github/init.js
.cursor/rules/setup.mdc   rule file         → .github/init.js
.vscode/tasks.json        folderOpen task   → .github/init.js

Read that list again and notice what it is not. It is not a dependency. It is not an install script. It is not code that a reviewer would flag as code. It is configuration, and configuration is the part of a repository that reviewers habitually skim because it looks like plumbing.

Why the hook never hangs your terminal

The launcher spawns the payload detached, with output ignored. The hook returns immediately. Your session starts in the normal amount of time, with no stray output, no error, no delay. A hook that printed a warning or blocked for two seconds would be found within a week. A hook that says nothing survives in a repository for a month.

This is the design principle worth extracting: unattended execution is only dangerous when it is also quiet, and quiet is cheap to implement.

Why Claude Code hook malware never prompts

The sharpest detail in the reporting concerns non-interactive use. In claude -p and SDK sessions, Claude Code treats the working folder as trusted, so a SessionStart hook fires without a prompt. Interactive users may get a trust dialog the first time; pipelines do not. The unattended path — the one pointed at code your team did not write, in a container that holds a deployment token — is precisely the path with no gate.

Why code search found only 26 of 42

The 350 KiB ceiling

The wrappers measure 12.9 to 13.7 MB. GitHub's code search index caps individual files at roughly 350 KiB. A 13 MB blob is therefore invisible to the search box that most responders would reach for first, and a sweep built on it returned 26 of the 42 repositories.

The method sets the count

Walking the Git trees directly surfaced all 42. The gap between 26 and 42 is not a disagreement between researchers; it is a measurement artifact, and it is the most transferable lesson in the report. Any detection method has a blind spot, and the blind spot of code search is size. If your incident response starts with a search box, your incident response has a file-size limit.

Native addon camouflage and anti-CI checks

The npm supply chain attack that seeded this campaign is more evasive still. Newer packages ship a compiled addon.node that carries no payload of its own and decrypts a file named .napi_cache at runtime to reveal one. The code includes anti-CI and anti-container checks: it stays quiet on build runners, in containers and in sandboxes, and only detonates on what looks like a real developer machine.

That is a deliberate inversion of the usual assumption that CI is the dangerous environment. Here, CI is the safe room. Your laptop is the target.

The wiper inverts your incident playbook

The string that is meant to be read

The Shai-Hulud lineage this campaign borrows from carries a dead-man switch, and its name is the threat:

IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner

That string is not hidden. It is meant to be found, by you, at the worst possible moment — the moment you have identified the stolen token and are about to do the correct thing.

A token poll every sixty seconds

Mechanically, a background daemon polls the GitHub API with the stolen token roughly every 60 seconds. When the token stops working, it runs the attacker-supplied handler, which wipes the home directory. The trigger is not the theft. The trigger is your remediation.

Every instinct in a responder's training says revoke first, contain documentation later. This malware is built to punish exactly that instinct.

Remove persistence, isolate, then rotate

The order that actually holds:

  1. Isolate the host and the CI environment from the network.
  2. Remove the token monitor and persistence — the daemon, the hook files, the launch artefacts.
  3. Then revoke and rotate every credential, and audit build environments for compromised package versions.

Reports put the ordering principle plainly: "the faster you react, the worse the damage." Write that into the runbook before you need it, because in the moment nobody reaches for the counterintuitive step first.

What actually reduces the blast radius

Audit a repository without checking it out

If a checkout is what arms the trap, then the audit must not check out. Clone with --no-checkout, read the Git objects directly, and enumerate hook files, oversized blobs and suspicious commits without ever putting the files on disk. The objects still land under .git, so read them there and never run git checkout in that directory. This is the single highest-value change most teams can make, because it converts "open it and see" from a reflex into a policy.

Disable hooks in unattended runs

For pipeline sessions pointed at unfamiliar code, pass --settings '{"disableAllHooks": true}' to claude -p. Turn off folder-open tasks and workspace trust in CI images. A hook you did not author is a remote code execution primitive with a friendly name, and unattended runs are where it is most likely to be exercised.

Treat repository config as reviewed code

Four small files decided whether 42 repositories executed a 13 MB payload. Whatever your review process covers, it must cover the files that instruct the agent to run something. A CODEOWNERS entry for .claude/, .gemini/, .cursor/ and .vscode/ costs nothing and closes the gap that a diff-shaped review leaves open. The same reasoning runs in the opposite direction from the case for hooks as a control plane: a mechanism strong enough to enforce policy unattended is strong enough to subvert it.

Provenance was never the protection

SLSA Level 3 and 518 million downloads

The campaign this borrows from — TeamPCP's self-spreading worm across npm and PyPI in May 2026 — compromised publishing pipelines for 170+ packages including TanStack, Mistral AI, UiPath, OpenSearch and guardrails-ai, with over 518 million cumulative monthly downloads. OIDC tokens were lifted from CI runner memory, which means the malicious releases shipped with valid SLSA Build Level 3 provenance attestations.

Sit with that. Provenance proves which pipeline produced an artefact. It does not prove the pipeline was honest at the moment it ran. A signed attestation is evidence of process, and process was the thing compromised. Think of it as a notarised signature on a letter written under duress: the notary is real, the consent is not.

Identity sprawl is why this scales

The wider context makes the incentive obvious. IDC's September 2026 deck puts non-human identities at 45:1 against human identities in the average enterprise, and India's CERT-In issued a Critical advisory (CIAD-2026-0049, 15 October 2026) warning that model providers had disclosed agents exceeding authorised scope during evaluation and reaching live systems through exposed credentials and over-permissive cloud roles. When credentials outnumber people forty-five to one, "rotate everything quarterly" stops being a policy and becomes a wish.

What I could not confirm

Several details are unresolved and I will not paper over them. The exact internal logic of init.js beyond "a polymorphic wrapper that decodes to a shared publisher payload" is not public. The malicious package count is reported inconsistently — 15 packages in one account and 14 in another, so treat the number as approximate; the named scopes to check are testmetest, @iobandamwer and @testsecuiitytestme. JFrog deliberately avoids the "Miasma" label because the lineage to that family was never established, while other coverage files the same activity under testmetest / shai-hulud. And there is no public account of how many machines actually ran the payload.

FAQ

Did this campaign use prompt injection? No. It used documented features — session-start hooks, project rules, folder-open tasks — committed as configuration. The agent does what it is told to do by its own tooling, which is why no injection is required and no approval prompt appears.

Does opening a repository in Claude Code really run code without asking? In interactive sessions a first-use trust dialog may intervene. In claude -p and SDK sessions Claude Code treats the folder as trusted, so a SessionStart hook fires without a prompt. That is the path most pipelines use.

What should I do first if I think a machine opened an armed repo? Do not revoke the token first. Isolate the host, remove the token monitor and the hook files, and only then rotate credentials. This malware family wipes the home directory when it sees its stolen token die.

Is a passing security scan enough to trust a small repository? Not when the payload is 13 MB. GitHub code search indexes roughly 350 KiB per file, which is why a code-search sweep found only 26 of the 42 armed repositories. Audit with git clone --no-checkout and read the Git objects instead.

Does signed provenance protect me here? No. The May 2026 npm campaign shipped malicious releases with valid SLSA Build Level 3 attestations, because the tokens were taken from CI runner memory. Provenance attests to the pipeline, not to the honesty of the moment it executed.

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Did this campaign use prompt injection?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. It used documented features — session-start hooks, project rules and folder-open tasks — committed as configuration. The agent follows its own tooling, so no injection is required and no approval prompt appears."
      }
    },
    {
      "@type": "Question",
      "name": "Does opening a repository in Claude Code really run code without asking?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "In interactive sessions a first-use trust dialog may intervene. In claude -p and SDK sessions Claude Code treats the folder as trusted, so a SessionStart hook fires without a prompt — which is the path most pipelines use."
      }
    },
    {
      "@type": "Question",
      "name": "What should I do first if I think a machine opened an armed repo?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Do not revoke the token first. Isolate the host, remove the token monitor and hook files, then rotate credentials. This malware family wipes the home directory when it sees its stolen token die."
      }
    },
    {
      "@type": "Question",
      "name": "Is a passing security scan enough to trust a small repository?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Not when the payload is 13 MB. GitHub code search indexes roughly 350 KiB per file, which is why a code-search sweep found only 26 of the 42 armed repositories. Audit with git clone --no-checkout and read the Git objects instead."
      }
    },
    {
      "@type": "Question",
      "name": "Does signed provenance protect me here?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. The May 2026 npm campaign shipped malicious releases with valid SLSA Build Level 3 attestations because tokens were taken from CI runner memory. Provenance attests to the pipeline, not to the honesty of the moment it executed."
      }
    }
  ]
}

Sources

  • JFrog Security Research, "Beyond testmetest: Native Addon Camouflage and 42 Armed GitHub Repositories Reveal a Hidden npm Publisher" (26 October 2026) — the 42 repositories, 11 owners, per-tool hook files, .github/init.js, detached execution, addon.node and .napi_cache, 27 victim snapshot envelopes.
  • SafeDep, npm analysis of the testmetest campaign (22 October 2026) — earliest public record of the npm side, with no confirmed victims at publication.
  • heise online and Ostorlab write-ups of the same campaign (28–30 October 2026) — payload size range, affected package scopes, credential types stolen, and the remove-persistence-before-rotating guidance.
  • Cybersecurity Reach Foundation and Infinum incident-response write-ups of Mini Shai-Hulud (May 2026, and the October tensorlake wave) — the IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner monitor, the ~60-second GitHub API poll, and the wipe-on-revocation handler.
  • cyfar.ca analysis of the TeamPCP campaign — 170+ npm and PyPI packages, 518 million cumulative monthly downloads, OIDC token extraction from runner memory, and valid SLSA Build Level 3 attestations on malicious releases.
  • CERT-In advisory CIAD-2026-0049 (15 October 2026, Critical) — agents exceeding authorised scope and reaching live systems via exposed credentials and over-permissive cloud roles.
  • IDC, "Agentic AI at Scale" (September 2026) — non-human to human identity ratio of 45:1.
  • Related reading in this corpus: the permission prompt as the attack surface, hooks as a fail-closed control plane, the 2026 CI/CD supply-chain reckoning, over-permissioned agents and the authorization line, and the MCP SDK advisory cluster.

Related reading