No malicious Zod 4.6.6 package was published to npm. Malicious commits reached Zod's GitHub repository on September 21, 2026, but its release workflow did not run for those commits. Standard Schema also never reached publishing. A proposed `@nubjs/nub@0.9.4` version entered npm's validation pipeline and was blocked before public installation. The related incident had three distinct publication outcomes, according to the [npm and GitHub incident account](https://github.com/orgs/community/discussions/208477) and [Zod maintainer Colin McDonnell's correction](https://news.ycombinator.com/item?id=50052814).

If your repository is compromised, finding bad code on GitHub is only the start. Check **which boundary the code crossed**: source history, release authorization, registry validation and artifact contents, then downstream consumption. Each requires its own evidence and response.

## What happened to Zod, Standard Schema, and Nub?

On September 21, related malicious changes appeared in the public repositories for Zod, Standard Schema, and Nub. The [incident responders](https://github.com/orgs/community/discussions/208477) linked the affected commits and described code designed to retrieve and execute further JavaScript during installation **if it reached a package and a user installed it**. The published record does not establish that this loader ran on a downstream user's machine. Commit metadata does not establish how the attacker obtained access, either.

The publication results matter more than a shared headline:

| Project | Where the malicious change reached | Published npm outcome |
| --- | --- | --- |
| `zod` | Public repository commit | Did not enter npm publishing; no malicious Zod version became installable |
| `standard-schema` | Public repository commit | Did not enter npm publishing; no malicious Standard Schema version became installable |
| `@nubjs/nub@0.9.4` | npm publishing pipeline | Held during validation and blocked before public availability |

The [incident team's publishing records](https://github.com/orgs/community/discussions/208477) support this table; a GitHub commit page alone cannot show those outcomes. The distinction matters: **Nub reached npm's pipeline, not a public installable release.** Submitting a version does not mean package consumers could fetch it normally.

The responders described the three repositories together, but McDonnell objected to wording that might suggest npm scanning stopped Zod. His [first-person account in the same discussion](https://github.com/orgs/community/discussions/208477) says Zod's release workflow was not triggered by the malicious source changes. Standard Schema likewise did not publish. npm's scanner caught the attempted Nub version. The detailed incident table and the maintainer's correction agree on those outcomes; they differ in what the broad framing might lead a reader to infer.

## Why did the Zod repository compromise stop before npm?

A Git push changes repository state. It does not, by itself, authorize an npm release. The maintainer says Zod used a dedicated GitHub deployment environment and [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) for its release CI; an ordinary commit or tag push could not trigger that job. He described a likely compromised Git push credential, while also saying the exact entry path remained under investigation. Treat that cause as a hypothesis, not a finding. [His account](https://github.com/orgs/community/discussions/208477) establishes the narrower point needed here: the malicious commits did not run Zod's release workflow.

Trusted publishing uses a configured CI identity and OIDC to authorize publishing without handing the workflow a long-lived npm token. It answers **who or what may request a publish**, not whether the resulting code is harmless. [GitHub environments](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments) can add branch restrictions and required approvals before a job runs or receives environment secrets. Your protection depends on the actual trigger, permissions, environment settings, and alternate publishing paths in your own repository. A compromised workflow or a different authorized publisher changes the analysis.

Nub illustrates the next boundary. Its release CI could be triggered by tag creation, according to the maintainer. The proposed `0.9.4` package reached npm, where publish-time validation flagged it and held it. [npm's publish-time scanning description](https://github.blog/changelog/2026-07-28-npm-publish-time-malware-scanning-and-dual-use-metadata/) distinguishes a package being submitted from one being made available for installation. Scanning stopped this attempted Nub version. It was not the control that stopped Zod or Standard Schema, because neither entered that pipeline.

Neither successful boundary rules out a direct Git dependency, downloaded source archive, or code run from compromised history. The [incident account](https://github.com/orgs/community/discussions/208477) reports no proven downstream execution through those routes or confirmed affected-install count.

## Why did Zod 4.6.6 release notes look alarming?

On October 12, a [Hacker News thread asking whether Zod 4.6.6 was compromised](https://news.ycombinator.com/item?id=50052814) pointed to a malicious September commit visible in the autogenerated commit list on GitHub's [`v4.6.6` release page](https://github.com/colinhacks/zod/releases/tag/v4.6.6). That list was a clue worth investigating. It was not evidence that the bad code was inside the npm tarball.

The malicious change had been reverted, leaving its commit in the range used for release notes. McDonnell [explained](https://news.ycombinator.com/item?id=50052814) that the code was absent from the published package. A source-history clue can easily be mistaken for a verdict on artifact contents. A tag can include a revert; a release page can list both the injection and its removal. Check what the build produced and what the registry made available before describing an install as infected.

There was a further change on **October 12**, separate from the September 21 response. McDonnell said he cleaned the malicious commits from history after weighing transparency against the risk that a historical checkout, such as one made by `git bisect`, could execute them. He also said that cleanup caused `v4.6.6` to lose npm provenance, and [`v4.6.7` followed](https://github.com/colinhacks/zod/releases/tag/v4.6.7). [His update](https://github.com/orgs/community/discussions/208477) describes a later history-management consequence. Lost provenance after rewriting history is not evidence that the earlier 4.6.6 package contained malware.

Provenance answers a narrower question. [npm's documentation](https://docs.npmjs.com/viewing-package-provenance/) describes attestations linking a published package to its source repository and build workflow. That link can help you investigate how an artifact was produced. It is not a malware scan, and an autogenerated GitHub release summary is not a substitute for examining the package.

## Which four boundaries should a maintainer check?

Apply these four checks to any suspected repository-to-package incident. Start with the observed event, preserve evidence, and advance only when the next boundary has its own proof. This response method draws on the Zod incident and the [npm](https://docs.npmjs.com/trusted-publishers/) and [GitHub](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments) release controls. It does not imply that every step below occurred in September.

### 1. Establish what changed in the repository

Record the suspicious commit IDs, affected files, author and push timestamps, branches and tags, and the time the change was removed. Preserve the original diff and logs before cleaning history. Inspect package manifests, lifecycle scripts, build scripts, and workflow files separately. A change that is inert in the repository may become active during build or install; a scary-looking commit may also have been reverted before any build ran.

Separate observation from explanation: “Commit X added code to file Y and was pushed at time Z” is an observation. “A stolen SSH key caused it” requires credential or access evidence. McDonnell's published account did not establish a final entry path, so an incident update should not invent one. [The incident record](https://github.com/orgs/community/discussions/208477) links the public changes without proving the initial credential compromise.

This boundary tells you **where bad source existed**, including historical checkouts, not whether a release ran. Preserve the evidence if you later rewrite public history.

### 2. Establish whether a release job could run, and did run

Read the workflow configuration rather than guessing from a tag. Which event triggers the publishing job: a push, tag creation, or manual dispatch? Does environment approval gate the job before it runs? Which branch or tag patterns are allowed? Who can edit the workflow and environment rules? Does npm accept only the expected OIDC publisher, or are direct-publish credentials and another workflow still authorized?

Check the run log for the suspect interval. Record the run ID, workflow file revision, triggering ref, actor, environment approval, and publish result. “The workflow has a manual trigger” and “no relevant run occurred” are different claims. A configuration screenshot taken after remediation cannot prove the rule in force at the time of compromise; preserve the relevant historical configuration and audit trail.

For this incident, the maintainer says the Zod release workflow did **not** run for the malicious commits. That is stronger evidence than assuming npm would have caught them. [Trusted publishing](https://docs.npmjs.com/trusted-publishers/) and [deployment environments](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments) explain the general controls, while [his statement](https://github.com/orgs/community/discussions/208477) gives the project-specific result.

### 3. Establish registry state and inspect the artifact

Keep four states distinct: a publish was attempted; a version entered validation; it was held or blocked; or it became publicly installable. For any public version, record the exact package name, version, publication time, tarball URL and digest, and the files inside the archive. Compare that artifact with the suspected payload and with the build output. An npm version string alone cannot answer what bytes consumers received.

Registry staff may have private validation logs. Record whose evidence supports each conclusion. The [incident account](https://github.com/orgs/community/discussions/208477) reports Nub's validation and block before availability, and no publish attempt from the malicious Zod or Standard Schema commits. A release page alone could not settle those outcomes.

If your package has provenance, use it to connect an artifact to the source and workflow that produced it. If provenance is missing, say so and investigate why. Do not treat a missing badge as a malware verdict or a present badge as proof of clean code. [npm's provenance guide](https://docs.npmjs.com/viewing-package-provenance/) defines the link the attestation provides.

### 4. Establish who could have consumed the code

Once you know whether a malicious artifact was publicly installable, search downstream evidence. Check lockfiles, install logs, caches, deployment manifests, and build records for the exact version and digest. Check Git URLs, direct source downloads, local tarballs, and historical checkouts separately. A registry block narrows exposure through the registry; it cannot answer whether someone obtained the same code another way.

Separate **availability**, **retrieval**, and **execution**. A public package could be available without appearing in your builds. A lockfile can name a version without proving a lifecycle script ran in a particular environment. A source archive could have been fetched without being executed. When the evidence stops at one stage, your notice should stop there too.

Preserve logs before token rotation, cache deletion, or repository cleanup erases the window you need to examine. Rotate or revoke credentials promptly when indicated, but verify that an old credential actually stopped working. Our [leaked credential closure checklist](/blog/leaked-credential-report-closure-checklist) separates exposure removal, revocation, and investigation. It does not replace the release-path checks above.

## How should you communicate the result and improve the release path?

Publish a short boundary-specific account. Name the repositories changed, the workflows that did or did not run, any version that entered validation, and any artifact that became installable. State the evidence source for each line and mark unresolved questions. A maintainer can say “we found malicious source history, but no malicious npm release” without minimizing the repository incident. A team with a public artifact must name its exact versions and affected consumers instead of saying only that “the repository was compromised.”

Keep dates separate. September 21 was the source compromise and blocked Nub submission. McDonnell's **September 24 edit** says his packages then moved to environment-scoped, manually triggered [staged publishing](https://github.com/orgs/community/discussions/208477). That was a response, not the control that had already stopped Zod. [npm's staging documentation](https://docs.npmjs.com/staged-publishing/) says staging a version does not itself require 2FA; approval to make it public does. A stage-only trusted-publisher setup can reject direct publishing, but [npm's configuration guidance](https://github.blog/changelog/2026-09-03-multiple-trusted-publishing-configurations-for-npm/) also allows multiple publishers. Check that no unintended direct route remains.

When several projects share a finding, coordinate the external update without flattening their outcomes. A repo-only incident, a blocked submission, and a public malicious package require different recipient lists and wording. Our [multi-vendor disclosure guide](/blog/coordinated-disclosure-multi-vendor-vulnerabilities) covers the separate problem of shared ownership and clocks.

Kit can help receive a researcher's report through a public [VDP intake](/blog/how-to-set-up-vulnerability-disclosure-program), give it an owner, track acknowledgment and remediation, and keep a disclosure record. Its CSIRT workflow can hold links to a fix or an external issue. The engineering team still has to inspect Git history, workflow authorization, npm status, and package bytes itself. Kit does not scan npm packages or certify a release as clean.

The practical lesson from Zod is a narrow one with wide use: **name the boundary before you name the blast radius.** Put repository, workflow, registry, and artifact evidence on one page. Then tell users exactly what was exposed, what was stopped, and what remains unknown. If you need a clear route for the next report, start with the [VDP setup guide](/blog/how-to-set-up-vulnerability-disclosure-program).