DevelopmentPlaybook23 min readPublished August 8, 2026

Repo-open time is the third execution path · 400+ packages per Wiz · revocation is the trigger

npm Package Compromise: What AI Teams Should Do Now

A compromised GitHub maintainer account seeded a self-spreading npm worm on August 4, 2026. Wiz reports it reached over 400 distinct npm packages, and the payload hunts AI-tool configuration alongside cloud keys — then attempts to persist through Claude Code hooks and VS Code tasks. The containment order is the counter-intuitive part: revoking the stolen token is what fires the payload.

DA
Digital Applied Team
Senior strategists · Published Aug 8, 2026
PublishedAugust 8, 2026
Read time23 min
SourcesWiz · Snyk · Socket · Aikido
Packages reached — Wiz
400+
distinct npm package names
vendor-stated, not a victim count
Files in the persistence commit
5
3 under .claude/, 2 under .vscode/
confirmed on GitHub's public API
Confirmed victims published
None
no verified public count — Snyk
at the time of writing
Revocation monitor poll
60s
Socket's teardown of the dead-man's switch
revocation is the trigger

The npm package compromise that began on Tuesday, August 4, 2026 treats the AI coding toolchain as both a target and a hiding place. Wiz reports the worm reached over 400 distinct npm packages after a GitHub maintainer account was compromised. The detail that should change how your team works is smaller and stranger: it attempts to persist through Claude Code hooks and VS Code tasks.

For two years, npm hardening advice has had two chapters. Chapter one is install-time: block lifecycle scripts, pin your lockfile, run a registry proxy. Chapter two, added more recently, is import-time: code that runs the moment a module is required, where --ignore-scripts buys you nothing. This incident opens a third chapter that almost nobody has written yet — repo-open time. The malicious commit planted a .claude/settings.json session hook and a .vscode/tasks.json folder-open task in the source repository, so the trigger is a developer or a coding agent opening the checkout.

This is a defensive brief. It covers what the vendors actually observed and where their numbers disagree, the containment order that inverts the usual credential-compromise reflex, the honest read on why a signed, attested release still shipped malware, and the standing controls that would have contained it. Everything below is sourced to Wiz, Snyk, Socket and Aikido, plus a handful of checks we ran ourselves against free public APIs.

Key takeaways
  1. 01
    Repo-open time is a real execution path now.Socket and Aikido both describe the autostart hooks running when a developer or an AI coding agent opens the cloned repository, with no npm install required. Snyk adds the necessary hedge: opening a checkout is an exposure condition, not proof code ran.
  2. 02
    Hunt the revocation monitor before you rotate anything.Socket's teardown describes a background watcher that polls the GitHub API every 60 seconds and evaluates a remote-supplied handler the moment its stolen token stops working. Both Socket and Snyk put containment before revocation for exactly that reason.
  3. 03
    The library code was never touched.Snyk and Socket independently found every file under dist/ byte-identical between the last clean release candidate and the compromised release. Only the package manifest and two added files changed — which is why a source diff of the library tells you nothing.
  4. 04
    Provenance attested a build whose source was already bad.Aikido describes valid provenance signed by GitHub Actions; Snyk describes an npm attestation under a trusted publisher; Socket confirmed a passing attestation directly. All three agree the signing pipeline behaved correctly. Provenance attests build integrity, not source integrity.
  5. 05
    The counts disagree, and no victim count exists.Wiz says over 400 distinct packages, Aikido says 444 across 1,381 versions, and Wiz's own public IOC list carries 446 names. Snyk states plainly that there is no verified public count of successful second-stage executions or exfiltrated credentials at the time of writing.

01The IncidentA maintainer account, a manifest, and two added files.

Wiz published its analysis on August 4, 2026 and describes the attack as beginning at 09:00 UTC that day, following what it calls the compromise of a GitHub maintainer account. Its headline scope figure is deliberately conservative: the worm has since propagated to over 400 distinct npm packages. That is a count of package names appearing in Wiz’s tracking, not a count of organisations, victims, or packages you personally depend on.

Snyk reconstructed the first hour against commit hashes. It enumerated every package npm returns for the maintainer account behind keyv and cacheable — the endpoint returned 61 package names — and found 11 malicious releases across the keyv family, the cacheable family and ecto. Snyk notes that early package lists omitting ecto are incomplete. The first of those releases, keyv@6.0.0, was published to npm at 09:35 UTC; we confirmed that timestamp independently from the npm registry’s own publish-time map, which retains the entry even after a version is removed.

The mechanics of the release itself are almost boring, and that is the point. Snyk and Socket independently found that every file under dist/ was byte-identical by SHA-256 between the last clean release candidate and the compromised release. Three things changed: the package manifest gained a preinstall script and two extra files in its published file list, and those two files were added. Snyk’s summary of the consequence is the one worth internalising — a developer does not need to import the library, start an application, or call a vulnerable API; resolving and installing the affected package is sufficient.

Spread — Wiz
distinct npm package names
400+

Wiz's own conservative wording for the worm's reach. It is a count of package names in Wiz's tracking — not victims, not organisations, and not packages in your tree. Wiz publishes no download or prevalence figure to go with it.

Vendor-stated · not a victim count
Maintainer-linked — Snyk
malicious releases of 61 packages
11

Snyk's denominator is one maintainer account: 11 malicious releases among the 61 package names npm returns for it. This is a different population from Wiz's 400+, which is other maintainers' packages reached by propagation. The two never add up.

Two populations · never fuse them
Victims
verified public count
None

Snyk states there is no verified public count of successful second-stage executions or exfiltrated credentials at the time of writing. Aikido's roughly 1,300 GitHub repositories are drop points, and Aikido says so. Do not convert repos, installs or downloads into victims.

Snyk · at the time of writing

One absence matters for anyone triaging this against a vulnerability feed. At the time of writing there is no CVE and no GitHub Security Advisory covering the incident. We pulled every npm advisory published in GitHub’s advisory database across the week of the attack — 95 records — and none of them mention keyv, cacheable, flat-cache, file-entry-cache or the malware family, and none carry the embedded-malicious-code weakness class. That matches Snyk’s own statement that no CVE or GitHub Advisory record had been assigned during its research. The one formal record that does exist is Snyk’s own advisory, which classifies the release as embedded malicious code.

If your detection posture assumes an advisory feed will tell you, this incident is the counter-example. Four security vendors analysed it and a national cyber agency picked it up before any CVE or GitHub Security Advisory record existed.

02Execution PathsThe third path: repo-open time.

Wiz states it plainly: persistence is attempted via Claude Code hooks and VS Code tasks.json. Snyk describes the pair as cross-wired — the Claude configuration registers a session-start command that invokes the script under .vscode/, and the VS Code task uses a folderOpen trigger to invoke the script under .claude/. Socket independently describes the same pair. Two vendors, one mechanism.

The reason this is a genuinely new control problem, rather than a colourful detail, is the trigger. Socket describes the autostart hooks executing when a developer or an AI coding agent opens the cloned repository, with no npm install required. Aikido reports the same effect in its own words. A team can have lifecycle scripts blocked everywhere, a strict lockfile policy and a registry proxy in front of npm, and still be exposed the moment somebody clones a repository and points a coding agent at it.

The mandatory hedge
Snyk limits the claim, and the limit should travel with it: “VS Code may ask before allowing automatic tasks, depending on workspace trust and user settings, so opening a checkout is an exposure condition rather than proof that code ran.” “Opening the repo owns you” is an overstatement. “Opening the repo is an exposure condition” is the sourced claim, and it is still enough to change your threat model.
Path 01
Install time
npm install resolves the package

The vector this incident actually used first: a preinstall script added to the published manifest. Snyk's guidance is a narrow lifecycle-script allowlist rather than blanket enablement, because some legitimate packages genuinely need install scripts.

Control: lifecycle-script policy
Path 02
Import time
require / import executes module code

Not the vector here — the library code was byte-identical, so nothing in the shipped module ran differently. This is the path the AsyncAPI incident exercised, and the reason install-script blocking alone is never a complete answer.

Control: import-time review
Path 03
Repo-open time
.claude/settings.json · .vscode/tasks.json

The new one. A session-start hook and a folderOpen task committed into the repository itself, triggered by opening the checkout rather than by installing anything. Vendor-documented, intended capabilities — used exactly as designed, by the wrong party.

Control: hook audit + workspace trust
Three npm execution paths compared — what triggers each, whether it requires an install, what this incident used on that path, and the vendor-documented control that limits it. Assembled from Snyk and Socket for the mechanism, the Claude Code hooks reference and VS Code Workspace Trust documentation for the controls.
Execution pathWhat triggers itNeeds an install?What this incident usedDocumented control that limits it
Install timenpm resolves and installs the packageYesA preinstall script added to the published manifest, plus two added payload files (Snyk)Lifecycle-script policy. Snyk recommends a narrow allowlist rather than enabling scripts globally.
Import timeApplication code requires or imports the moduleNo, once the package is on diskNot this vector. Snyk and Socket both found the shipped library files byte-identical to the clean release.Import-time review and behavioural scanning; lockfile pinning so a fresh version cannot arrive silently.
Repo-open timeA developer or a coding agent opens the cloned repositoryNo (Socket, Aikido)A session-start hook in .claude/settings.json and a folderOpen task in .vscode/tasks.json, cross-invoking each other’s loader (Wiz, Snyk, Socket)Claude Code’s /hooks browser, allowManagedHooksOnly and disableAllHooks; VS Code Workspace Trust.

We have written the first two rows of that table before. The install-time row is the subject of our guide to npm 12 blocking install scripts by default, and the import-time row is the whole story of the AsyncAPI import-time npm attack, where install-script blocking bought defenders nothing. The third row is what this incident adds, and it is the row your existing hardening checklist almost certainly does not cover.

There is a second, quieter reason AI-tool config matters here. Aikido reports that the generic filesystem scanner in the payload runs roughly 200 glob patterns and explicitly targets IDE configuration files including .vscode/tasks.json and .claude/settings.json. So the same files are both the persistence mechanism and a theft target. Whatever sits in your agent configuration — endpoints, project context, tokens somebody pasted in once — is in scope.

03LineagePersistence relocated into the AI toolchain in under three months.

The most useful thing in Wiz’s write-up is not the package count. It is the lineage. Wiz describes the payload as a descendant of the “Mini” Shai-Hulud malware family and says it is heavily based on public repositories of that codebase published by the group Wiz labels TeamPCP. That gives us a dated before-and-after against Wiz’s own earlier research.

On May 19, 2026, Wiz published an analysis of an earlier wave in the same family. That payload also used a downloaded Bun runtime for secondary execution and also exfiltrated through attacker-created public GitHub repositories — but it persisted through a Python-based backdoor polling GitHub for signed messages. Operating-system-level persistence, in other words: the ordinary place malware hides.

By August 4, the same lineage persists through a session hook in an AI coding agent and a folder-open task in an editor. Socket makes the same observation from the technique side, describing the autostart hooks as among the techniques not documented in earlier reporting on this family. In under three months, a malware family moved its persistence out of the operating system and into developer tooling — because that is where the trusted, committable, automatically-executed configuration surface now lives. Agent config is the new autostart directory, and it is version-controlled and shared by design.

May 2026
OS-level backdoor
Python backdoor polling GitHub for signed messages

Wiz's May 19 analysis of an earlier wave in the same family: a downloaded Bun runtime for secondary payloads, exfiltration through attacker-created public GitHub repositories, and persistence sitting at the operating-system layer.

wiz.io · published 2026-05-19
August 2026
Agent-config persistence
.claude/settings.json session hook · .vscode/tasks.json folderOpen task

Same lineage, same Bun-based second stage, same public-repo exfiltration — but persistence has moved into committable developer tooling. Socket calls the autostart hooks a technique not documented in earlier reporting on this family.

Wiz, Snyk, Socket · 2026-08-04
Attribution ceiling — read before you brief anyone
The sources disagree on confidence, and the honest position is the narrower one. Wiz asserts the lineage to a codebase TeamPCP published. Socket explicitly declines named attribution, saying the self-identifying markers that would tie this to a named campaign were not recovered from the sample. SC Media explains why both can be true: the earlier source code was publicly released, so a separate actor may be responsible. You may say the payload is derived from a codebase TeamPCP published. You may not say TeamPCP carried out this attack — and summaries circulating that claim are over-attributing.

One naming note, for the same reason. CSA Singapore’s advisory calls the malware “ChainDrop”. That label appears in the agency notice and in none of the four vendor write-ups, so it is not a consensus name. Cite the advisory as evidence that a national cyber agency picked this up, but note that its references point back to the vendors and its version count and remediation steps track theirs closely — it is government uptake, not a second measurement.

04Containment OrderHunt first, rotate second.

The industry reflex on credential compromise is to rotate immediately. Here, rotating first is the trigger. Socket describes a host-level dead-man’s switch: the stolen GitHub token and a handler command are written to a private config directory, the component persists as a macOS LaunchAgent or a Linux user-level systemd service configured to survive logout, and a watcher script polls the GitHub API every 60 seconds. On an HTTP 4xx — the moment the token is revoked or rotated — it evaluates a remote-supplied handler string, clears its own state and exits. It also self-clears after a 24-hour time-to-live.

Socket, on what removes the ambiguity
“No linter, secret scanner, or release tool installs a background service that watches for its own credential to be revoked and then evaluates a remote-supplied string. This is the artifact that removes any ambiguity about intent.” Socket’s Threat Research Team, in its teardown of the payload. Wiz describes the same mechanism from the other side, noting the switch is selectively armed based on a response from the command-and-control infrastructure.

Both Socket and Snyk therefore put containment before revocation, and Snyk notes it has analysed this same monitor pattern in a prior npm compromise. Socket’s instruction is one sentence: “Before rotating any credential, hunt for and remove the host-level dead-man’s switch. Revocation is its trigger.” The table below reconciles Socket’s remediation sequence with Snyk’s six-step version and Wiz’s higher-level recommendations into one ordered checklist, with the reason for the ordering attached to each step.

Ordered containment checklist for a suspected host compromise, reconciling Socket’s remediation sequence, Snyk’s six-step version and Wiz’s recommendations, grouped into contain, revoke and clean-up phases, with the reason for each ordering decision and the exact artefact or path to check.
StepWhy this orderExact artefact or pathSource
Phase 1 — contain, before you revoke anything
1 · Isolate the host, preserve logsEverything after this is destructive to evidenceShell history, process list, launch-agent and systemd unit listingsSnyk
2 · Hunt the revocation monitorIt polls every 60 seconds and fires on revocation~/.local/bin/gh-token-monitor.sh; ~/.config/gh-token-monitor/; ~/Library/LaunchAgents/com.user.gh-token-monitor.plist; ~/.config/systemd/user/gh-token-monitor.service; /tmp/gh-token-monitor.out.log and /tmp/gh-token-monitor.err.logSocket, corroborated by Snyk
3 · Disable persistence, keep a forensic copyDeleting without unloading leaves a live processUnload the LaunchAgent on macOS; on Linux disable the user service and its lingering sessionSocket + Snyk
4 · Remove the repo-level implantOtherwise reopening the checkout re-arms everything.claude/settings.json, .claude/setup.mjs, .claude/math_init.js, .vscode/setup.mjs, .vscode/tasks.jsonWiz; file list confirmed on GitHub’s public API
Phase 2 — only now, revoke and rotate
5 · Revoke, do not merely rotateSocket assumes an equivalent monitor may watch the npm token toonpm and GitHub tokens, including CI-held copiesSocket
6 · Rotate everything reachable, from a clean machineRotating from the affected host re-exposes the new secretCloud, secret-manager, Kubernetes and CI credentials; crypto keystores if presentSocket + Snyk
Phase 3 — clean up what the registry cannot
7 · Review audit logsEstablishes whether the second stage actually ranAccount, cloud and source-control audit logs; unexpected publishes, repositories and commitsSnyk + Wiz
8 · Purge proxies and cachesnpm removal does not delete copies already stored internallyPrivate registry proxies, CI caches, developer package cachesSnyk
9 · Pin away from the affected rangeCaret and tilde ranges can pull a fresh releaseSnyk names the latest clean stable 5.x as the fallback and warns a downgrade may require code changes; Socket suggests blocking the whole scope in a registry proxySnyk + Socket

Two live-state notes on that last row. Socket’s warning was written under an August 4 condition — while the maintainer account remained compromised — and should not be restated as a current statement of that account’s status. Separately, the state on npm has moved since the vendor posts: the compromised release is gone from the registry and the latest tag has fallen back to the previous stable release, published in January 2026. Snyk’s August 4 snapshot recorded eight malicious releases still carrying the latest tag, so read that observation as dated to the day it was made.

The rotation half of this is a standing capability, not an incident task. If step 6 is a multi-day scramble for your team, the fix is upstream of any single incident — our credential rotation reference covers zero-downtime rotation and short-lived tokens, and the process wrapper around all of the above is in our incident-response playbook for agentic workflows.

05ProvenanceThe signing pipeline worked perfectly.

This is the part most worth getting right, because the wrong lesson is easy and expensive. The malicious release shipped with valid provenance. Aikido’s wording is that the poisoned versions were published to npm with valid provenance signed by GitHub Actions. Snyk’s is that the npm manifest identifies GitHub Actions as the trusted publisher and links to an npm attestation for the release, and that the malicious source was present in the tagged repository state, so the legitimate workflow built and attested the malicious artifact. Socket confirmed it directly, describing a passing attestation because the legitimate release workflow built already-trojanized source. Three vendors, three independent observations, one conclusion.

Use each source’s own vocabulary rather than upgrading it. “Valid provenance signed by GitHub Actions” is Aikido’s phrasing; “npm attestation” and “trusted publisher” are Snyk’s; sigstore provenance is Socket’s. Reaching for a grander supply-chain-framework acronym overstates every one of them.

Socket, on what provenance actually attests
“The lesson is that provenance attests build integrity, not source integrity. The npm and sigstore pipeline did exactly what it is designed to do and still produced a signed, verifiable attestation for malware, because the source it built from was already trojanized.” Snyk states the same boundary from the other direction: provenance remains valuable evidence about build origin, and this incident shows its boundary — provenance can faithfully attest a build whose source or workflow context has already been compromised.

Socket is careful to separate two facts that read as one, and the separation matters. The first is a capability: the payload itself contains a provenance component that builds attestation envelopes and submits transparency-log entries, so republished versions can carry freshly minted provenance rather than merely inheriting it. The second is an observation: the original malicious release carried a passing attestation because the legitimate workflow built trojanized source. Socket says explicitly that these are two distinct facts. Fusing them produces a much scarier and much less accurate story.

The same care applies to commit signatures. It is tempting to write that the malicious commits were signed, and that is wrong in a way that matters. One of the two commits is signed and verified; the other, the release commit itself, is unsigned under the maintainer account’s own identity. Snyk’s reading is the correct one.

Snyk, on what a verified badge proves
“A verified badge proves that GitHub signed the commit object. It does not establish that the change was authorized by the project maintainer. The evidence supports compromise of an account, credential, session, or release path. It does not identify the person operating it, and the maintainer should be treated as an incident victim.” That last clause is not a courtesy. The maintainer account behind keyv and cacheable is the first victim in this incident, and coverage that turns the individual into the story is getting the security lesson wrong as well as the ethics.

There is a related trap in the release process itself. Snyk reports that the release commit also added a test which executed the loader through a direct process call, and that a later commit removed only that test — meaning running the release test suite could itself have executed the payload in CI. If your pipeline treats “tests passed” as a safety signal, that is the shape of the failure to think about.

One piece of context that must not be misread: GitHub published a post on July 28, 2026 describing supply-chain hardening for npm and GitHub Actions shipped over the preceding months. It predates this attack by a week and is preventive work, not a response to it. It is fair to note that the hardening was already in flight and did not prevent this — that is the honest read, and it points at protecting the source and release path rather than at more signing.

06ScaleThe counts disagree, and that is the useful finding.

Aggregator coverage of this incident has fused several different measurements into single confident-sounding figures. They are not the same measurement. Wiz’s prose says over 400 distinct npm packages; its own public indicator list carries more names than that; Aikido’s tracker gives a different pair of numbers again; and the download figures in circulation come from at least three sources using at least two different time units. Publishing the spread is more useful than picking a number, and it inoculates you against the fused versions.

A source-by-source scorecard of package counts, version counts and download or install figures reported for the npm compromise, each with its unit and measurement window, and whether the source is an independent measurement or a restatement of another source.
SourcePackage countVersion countDownload / install figure, with unitIndependent or derivative?
Wiz (blog post)“over 400 distinct npm packages”Not statedNone. Wiz publishes no download or prevalence figure in its text.Independent — primary research
Wiz’s public indicator list446 unique package names2,317 versionsNot applicableIndependent — we parsed the published file ourselves
Snyk11 malicious releases among the 61 packages returned for one maintainer account11 releases enumerated by timestampkeyv 619,682,667 · flat-cache 579,751,309 · file-entry-cache 571,240,025 — all July 5 to August 3, 2026, i.e. monthlyIndependent — cites the npm API and names its window
SocketNot stated as a totalNot stated as a total“tens of millions of weekly downloads”, collectively — deliberately impreciseIndependent — payload teardown
Aikido444 packages1,381 versions“over 2 billion monthly installs” combined; its per-package figures differ between two places in the same postIndependent, but treat its download figures as approximate
CSA Singapore advisoryLists the same 11 named packages“over 1,300 package versions”“a combined 2 billion monthly downloads”Derivative — its references list Wiz, Socket and Aikido
SC Media“400-plus packages”, restating WizNot statedNoneTrade coverage — valuable for the attribution nuance

Two rules make that table safe to use. First, never mix units: a monthly download count and a weekly download count are different measurements, and an install count is a third thing again. Snyk is the only source that names both a figure and its window, which is why it is the one to quote if you need a number at all. Second, Snyk attaches the caveat that must travel with any of these figures — those numbers overlap heavily because the packages depend on one another and appear in the same toolchains, they are measures of ecosystem reach rather than counts of compromised hosts, and the exposure window for each malicious version was much shorter than a month.

The reach is wide for a structural reason rather than a popularity one. Socket puts it precisely: “These are foundational packages that sit deep in dependency trees, so their reach is largely indirect. A common chain is eslint to file-entry-cache to flat-cache to keyv, which means most affected users never install any of them directly.” If you are checking whether you are affected, a direct-dependency grep will not answer the question. The lockfile will.

The shape of the indicator list is the other thing the aggregate hides. Parsing Wiz’s published file, the 446 package names are heavily concentrated in a small number of scopes — mostly a handful of organisations’ internal package families rather than 446 household-name libraries. That is the honest scale of it, and it changes who should be worried most.

Where the 446 package names sit · share of Wiz's indicator list by scope

Source: our own parse of Wiz's published indicator list · shares are of the 446 package names it contains, not of npm, not of victims
@servicetitan141 of 446 package names
31.6%
@onereach78 of 446 package names
17.5%
@or-sdk74 of 446 package names
16.6%
@ornikar42 of 446 package names
9.4%
Unscoped packages36 of 446 package names
8.1%
@qlik28 of 446 package names
6.3%
@nebula.js21 of 446 package names
4.7%
All other scopes18 of 446 package names
4.0%
@umacloud8 of 446 package names
1.8%

One more denominator, because it is the single most misquoted figure in this incident. Wiz reports that the credential-theft target list was expanded by almost 70 percent. That is growth in the number of credential-theft targets relative to the earlier public codebase this payload derives from. It is not 70 percent of packages, not 70 percent of victims, and not 70 percent of installs. It is also the only percentage in Wiz’s entire post — if you see a second percentage attributed to Wiz, it did not come from Wiz.

What the expanded list contains is the reason this post exists. Wiz names AI-agent credential stores for Claude, OpenAI, Codex, Cursor and Gemini among the new additions, alongside crypto keystores, self-hosted CI and registry secrets, additional cloud CLI configurations, and system files. The AI toolchain has graduated from incidental collateral to a named collection target. We saw the earlier version of that trend in the previous attack aimed squarely at AI infrastructure, and in the way AI-hallucinated package names widen the npm attack surface from the other end.

Do not convert reach into victims
Aikido reports roughly 1,300 public GitHub repositories carrying the campaign’s marker string, and describes them as drop points for encrypted credential bundles — not as confirmed victims. Snyk states the position for the whole incident: there is no verified public count of successful second-stage executions or exfiltrated credentials at the time of writing. Repositories are not victims, installs are not breaches, and downloads are not compromised hosts.

07Standing ControlsThe controls that already exist.

The strongest defensive framing here is also the simplest: the vendor documents this exact threat model, and the attack is a faithful instance of it. Claude Code’s hooks reference describes SessionStart as firing when a session begins or resumes, lists .claude/settings.json as project-scoped and shareable — explicitly committable to the repository — and carries a security disclaimer that reads like a description of this incident written in advance.

Anthropic’s own documentation
“Command hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.” The shareability of project-scoped hook configuration is a documented, intended capability — not a bug. This attack used it exactly as designed, from the wrong side.

Three documented controls follow from that, and they are the first things to check. The in-product /hooks browser is read-only and shows every hook event with a count of configured hooks, lets you inspect each handler, and — crucially — shows which settings file each hook came from. That is the single most useful “did something plant a hook in this repository?” check available, and it is built in. For managed fleets, allowManagedHooksOnly lets administrators block user, project and plugin hooks, and disableAllHooks exists as a setting, though it cannot disable managed hooks.

Be careful about what is and is not gated by workspace trust. The documentation states that frontmatter hooks in a project subagent run only after you accept the workspace-trust dialog for the folder the agent file came from, and notes that before a specific version those hooks could run from folders you had not trusted. That sentence is scoped to subagent frontmatter hooks. The documentation does not state whether .claude/settings.json hooks are gated by the same dialog, and it does not state that they are not. Do not extrapolate in either direction — including from this post. Run /hooks against the version you actually have installed and see for yourself. On the editor side, VS Code’s Workspace Trust is the mechanism governing the automatic-task path, which is what Snyk’s hedge points at.

Install time
Lifecycle-script allowlist

Snyk's framing is the honest one: disabling lifecycle scripts limits this execution path, but some legitimate packages require install scripts, so maintain a narrow allowlist rather than enabling scripts globally. Blanket enablement and blanket disablement both fail in practice.

Allowlist, not a switch
Repo-open time
Audit hooks with /hooks

A read-only browser that shows every hook event, each handler, and which settings file it came from. Make it a step in the same reflex as reading a Dockerfile before you docker build a stranger's repository. Verify on your own installed version rather than trusting any blog post.

Run it before you open
Repo-open time
Workspace Trust, kept on

The editor-side control for the automatic-task path. Snyk's hedge is that VS Code may ask before allowing automatic tasks depending on workspace trust and user settings — which is only true if you have not clicked through the dialog on every repository you have ever opened.

Keep restricted mode
Blast radius
Short-lived, scoped credentials

The dead-man's switch is only interesting because a long-lived token was sitting on a developer machine. Machine identities that expire in minutes and are scoped to one repository make both the theft and the revocation-monitor trick far less valuable to an attacker.

Assume the host is hostile

The organisational version of this is a policy question, not a tool question: what is your team’s rule for inheriting a repository’s agent configuration? Most teams do not have one, because until now agent config was a productivity artefact rather than an execution surface. Our write-up of Claude Code team-adoption anti-patterns is the natural home for that rule, and the standing answer to stolen machine identities sits in our non-human credential access playbook. Writing that rule down, and wiring it into how a team adopts coding agents in the first place, is one of the first things we set up in our AI and digital transformation engagements.

There is an uncomfortable timing coincidence worth naming. The same week this attack proved that shareable agent configuration is an execution surface, the industry shipped an open standard for portable, shareable agent extensions. That is not an argument against the standard — portability is plainly the right direction, and a common format makes auditing easier, not harder. It is an argument that the review discipline has to arrive at the same time as the distribution mechanism, and historically it does not.

08VerificationHow we checked, and what we could not confirm.

Most coverage of this incident restates Wiz. We went to the free public APIs instead. Each check below is reproducible in minutes, which is the point of printing them.

The persistence commit. Using GitHub’s public REST API for the affected repository, the commit whose short hash is d8c850c7 was authored at 09:04:30 UTC on August 4, 2026 under the commit message chore: update config, and adds exactly five files: .claude/math_init.js, .claude/settings.json, .claude/setup.mjs, .vscode/setup.mjs and .vscode/tasks.json. GitHub reports it as verified with reason valid, under an automation-bot author identity, and the commit body carries a co-authorship trailer naming a coding agent. That is first-hand confirmation of Wiz’s persistence claim, Wiz’s commit-message claim and Aikido’s observation that the commits are dressed in an agent’s identity — from one API call, no vendor restatement involved.

The release commit is a different shape. The commit two minutes earlier, tagged as the version release, is attributed to the maintainer’s own identity and is reported as unsigned, while the persistence commit that followed is signed and verified. That asymmetry is why “the malicious commits were signed” is the wrong summary.

The registry state. The compromised release is no longer retrievable from npm, and the latest tag points back to the previous stable release from January 2026. The attestation URL that Snyk cites for the compromised release now returns a not-found response, which is why we have not linked it — the provenance finding rests on three vendors’ contemporaneous observation, not on a currently-live artifact.

The advisory sweep. Pulling every npm advisory published in GitHub’s advisory database across the week of the attack returned 95 records, none of which relate to this incident. That is the basis for the no-CVE, no-advisory statement above, and it is narrower and more checkable than “there is no advisory anywhere”.

What we did not resolve. The vendors’ indicator tables do not fully agree — Socket and Aikido describe the two loader files as identical with a single hash, while Wiz lists two different values in a different hash algorithm — so we have not printed per-file hashes as settled fact; use the vendor advisories directly for indicators. The sources also describe the propagation discovery step slightly differently, so we have described it in plain language rather than picking one endpoint. And Socket’s detection-speed figures, an average of five minutes and eighteen seconds across the campaign, are telemetry about Socket’s own product — a real data point about that product, not an industry benchmark.

Read the primaries
The four vendor analyses this post draws on are Wiz, Snyk, Socket and Aikido. The lineage evidence is in Wiz’s May analysis of the earlier wave; the attribution nuance is cleanest in SC Media; and the government uptake is CSA Singapore’s advisory. Indicators move during a live incident — take them from the vendors, not from a blog post.

09ConclusionAgent config is now production surface.

What to take from this

Treat agent configuration the way you already treat CI configuration.

Strip out the package names and the hash tables and one structural change remains. A malware family that persisted through an operating-system backdoor in May persisted through a coding agent’s session hook and an editor’s folder-open task in August. Nothing about that required a vulnerability. It required a configuration file that is designed to be committed, shared, and executed on open — and a team culture in which nobody reads it.

The practical consequence is a third column in every npm hardening checklist. Install-time controls remain necessary and remain insufficient. Import-time review remains necessary and remains insufficient. Repo-open time is the path that bypasses both, and the controls for it already exist and are documented — a hook browser, a managed-hooks setting, workspace trust. They are simply not in anybody’s onboarding checklist yet.

Expect this shape to repeat rather than to be a one-off. Every portable agent format, skill marketplace and shareable configuration standard shipping this year widens the same surface, and the review discipline reliably lags the distribution mechanism by a release or two. The teams that come out of this well will be the ones that decided, before the next incident, what their rule is for inheriting a stranger’s agent configuration — and that practised the containment order, so that when a token is stolen the first instinct is to hunt rather than to revoke.

Harden the AI development toolchain

The controls for repo-open execution already exist. They are just not in anyone’s onboarding checklist.

Our team helps engineering and marketing organisations harden the AI-assisted development toolchain — dependency policy, agent configuration review, credential rotation and incident readiness, delivered in days not quarters.

Free consultationExpert guidanceTailored solutions
What we work on

AI toolchain security engagements

  • Dependency and lifecycle-script policy that teams will actually keep
  • Agent and IDE configuration review as a standing control
  • Credential rotation design — short-lived, scoped, revocable
  • Incident-readiness rehearsals for supply-chain compromise
  • Build and release path hardening beyond signing
FAQ · npm compromise response

The questions teams are asking this week.

Wiz reports that on August 4, 2026 a GitHub maintainer account was compromised, and that a self-spreading worm has since propagated to over 400 distinct npm packages. Snyk reconstructed the first hour: 11 malicious releases across the keyv family, the cacheable family and ecto, drawn from the 61 package names npm returns for that one maintainer account. The published library code itself was untouched — Snyk and Socket independently found the shipped files byte-identical to the previous clean release. What changed was the package manifest, which gained a preinstall script and two extra payload files. The payload targets cloud credentials, infrastructure secrets, crypto keystores and, notably, AI-agent credential stores for Claude, OpenAI, Codex, Cursor and Gemini.
Related dispatches

Continue exploring supply-chain security.