News
Zod's Git History Still Contains the Malware Commit: Inside the npm Supply-Chain Attack That Never Shipped (2026)
On September 21, 2026, a compromised git credential pushed a crypto-funded malware loader straight into zod's main branch. npm's publish-time scanning caught it before it reached your node_modules, but the commit sat in the repo's history for three weeks, and that gap is the real lesson. If you're listing a product on a directory like SaaSCity, your dependency tree is part of the thing getting reviewed.

Contents (8)
- What actually happened, in order
- The part that actually stopped it
- "Reverted" isn't "gone"
- Why this keeps happening to packages everyone trusts
- The detail that should actually worry you
- What this costs a founder who isn't paying attention
- The honest disclosure, since you're reading this on a directory's blog
- Do this before your next deploy, not after
Three weeks ago someone pushed malware into one of the most trusted packages in the JavaScript ecosystem, and most of you didn't hear about it until yesterday, because the fix left the evidence sitting right where anyone could trip over it.
On October 11, 2026, a developer auditing zod 4.6.6 before upgrading opened GitHub issue #6658, titled "Suspicious preinstall script in v4.6.6 commit history." The malware itself never shipped. What did ship, still, three weeks after it was supposedly fixed, was the commit that carried it, still attached to the release, still linked from the GitHub release page. By October 12 the thread had reached the Hacker News front page at 156 points. If you build anything on npm, the next ten minutes are worth your time.
What actually happened, in order
On September 21, 2026 at 11:56:52 UTC, a commit landed on the main branch of colinhacks/zod, the TypeScript validation library that roughly every second Next.js project you've opened depends on. The commit message read "Fix bugs and improve performance." It was anything but. Someone had gotten hold of a git push credential tied to the project and pushed directly to main, skipping pull requests and code review entirely.

The same hour, the same pattern and the same misleading commit message hit two more repositories: standard-schema/standard-schema at 12:37:19 UTC, and nubjs/nub across two commits at 12:54:48 and 13:00:18 UTC. Three unrelated-looking projects, one attacker, one playbook. We're describing the mechanism in general terms rather than handing anyone a blueprint, but the shape of it matters. The planted script ran during package install, reached out to public Ethereum infrastructure, pulled a delivery address out of a smart contract, fetched a second-stage payload from wherever that address pointed, ran it with the install process's own permissions, then cleaned up after itself so nothing obvious was left behind.
The smart contract part is the detail worth sitting with, and we'll come back to it.
The part that actually stopped it
This is a real "the system worked" story, not just damage control. Of the three compromised repos, only @nubjs/[email protected] made it as far as npm's publishing pipeline. npm's own infrastructure held that version in validation, flagged the malicious behavior, and blocked it before a single npm install could pull it down. The zod and standard-schema changes never got that far. They sat in git history and never built into a published tarball.

npm posted the full account the same day in GitHub Community discussion #208477, framing it as a shift from "publish, detect, remove" to "scan, assess, prevent." The old standard npm incident was a bad package going live, getting installed by thousands of projects, and getting yanked after the damage was already done. This time automated scanning caught it at the gate. zod's own release pipeline helped too: releases run through trusted publishing plus a GitHub environment that requires an explicit workflow_dispatch, so even a compromised main branch couldn't push a bad build out through normal CI. Colin Hacks reverted the malicious commit that same day (PR #6620, revert commit 10dda3a) and later moved all of his packages to environment-scoped staged publishing that requires 2FA on every single publish, not just account login.
That's a good outcome. So why is this still news three weeks later?
"Reverted" isn't "gone"
A revert is a new commit that cancels the effect of an old one. It doesn't erase the old one. The malicious commit d821685 stayed perfectly reachable in zod's git history, and it was still linked directly from the v4.6.6 GitHub release page.

That's exactly what tripped up the developer behind issue #6658. Auditing a dependency before upgrading, as any careful team should, they ran into a commit whose history didn't match its own message. The published tarball on npm is clean. If you installed zod 4.6.6 through a normal npm install, you never touched the loader. But a git bisect, a git blame, or a direct checkout of that SHA lands you right back on it, preinstall script and all. For three weeks, the only thing separating "this is historical and handled" from "this is live and dangerous" was context that lived in a GitHub discussion most people never read.
Think about what that means for your own incident response. If you ever have to clean up a compromised credential, a leaked key, or a bad commit, ask whether you fixed the effect or fixed the cause and then removed it from every place a future reader, human or script, might stumble into it. Revert the code. Rotate the credential. Then go back and scrub the tags, the release notes, the pinned commit links, and the cached artifacts. "Reverted" and "remediated" are not the same word.
Why this keeps happening to packages everyone trusts
zod isn't some fly-by-night dependency. It has zero runtime dependencies, 1,012 published versions, and roughly 290 million weekly downloads with more than 131,000 projects depending on it directly. That last number is the one that should stop you.

A zero-dependency, single-maintainer library is about as short and simple as a supply chain gets. There's no sprawling tree of transitive packages to worry about. It's basically "trust Colin Hacks's code, full stop." And it still almost shipped a loader, because the real trust boundary was never "who wrote this library." It was "who holds the git credential that can push to its main branch," which is a much softer target than the code itself, and the one attackers have been going after all year.
This wasn't an isolated event. It's the shape of 2026 so far:
| Incident | Date reported | Entry point | Scale |
|---|---|---|---|
| @tanstack/zod-adapter malware | May 19, 2026 | Compromised npm maintainer account | Part of the "Mini Shai-Hulud" wave |
| ChainDrop worm | Aug 4, 2026 | Compromised GitHub account (Keyv maintainer) | 1,300+ packages, 2 billion combined monthly downloads |
| zod / standard-schema / nub | Sep 21, 2026 | Compromised git push credential | 1 package briefly reached npm, held and blocked |
The ChainDrop attack is worth studying because it shows what happens when the same move actually lands. A Shai-Hulud-derived worm compromised a maintainer's GitHub account, pushed to main, and let the projects' own GitHub Actions workflows build and publish the poisoned versions, so the malicious releases carried entirely valid provenance records. The project's own trusted pipeline really did publish them. Aikido counted at least 868 affected packages across 1,381 versions, including Keyv, Cacheable, flat-cache, and file-entry-cache, reaching companies like Deliveroo, Picsart, and ServiceTitan. The payload there was a straightforward infostealer: a preinstall hook pulled down the Bun runtime and used it to run an obfuscated credential harvester.
Same door, different lock. In zod's case the door held. In ChainDrop's case it didn't.
The detail that should actually worry you
Set the install script mechanics aside for a second; those are old news, and tooling already defends against them (more on that below). The genuinely new and uncomfortable detail is where this loader got its instructions: a smart contract on a public Ethereum network.
A traditional malware domain can be sinkholed, blocklisted, or seized. A delivery address stored in a smart contract can't be taken down the same way, and the attacker can update what it points to without ever touching the npm registry or publishing a new package version. That separates "the malicious code is live" from "the malicious code is discoverable by anyone scanning package contents," because the dangerous part of the logic isn't in the code you can read. It's in wherever the chain happens to point today. Security tooling built around spotting a known-bad domain or hash will miss that every time. The question has to become whether a package talks to the network at all during install and why, not whether some specific string looks suspicious.
What this costs a founder who isn't paying attention
If you're running a SaaS, your dependency tree is your attack surface whether you've thought about it that way or not. Here's what's worth doing, roughly in order of how cheap it is:
- "It's just a dev dependency" is not a safety argument. Preinstall and postinstall hooks run before your application code loads a single line, with your permissions, on your machine or on your CI runner holding your deploy secrets. A dev-only package has the same install-time blast radius as a production one.
- A human reading the diff didn't save anyone here. Infrastructure did: npm's scanning, plus a release pipeline that physically couldn't publish without a manual trigger. If your own deploy pipeline pulls packages from anywhere without an equivalent gate, you're the only gate standing there, which is a bad place to be the whole plan.
- Pin your lockfile and use
npm ci, notnpm install, in CI.npm ciinstalls exactly what the lockfile says and fails instead of silently re-resolving a dependency to a newer, possibly compromised version. - Turn off install scripts you don't need. pnpm disables them by default, and current npm versions increasingly follow suit, though plenty of Node installs still run the older default that executes them automatically. Allowlist the handful of packages that have a legitimate reason to run one.
- Scope and 2FA-back every publish token you hold. npm's own policy is already moving this direction: tokens that bypass 2FA for account changes are being restricted as of August 2026, with direct publishing further restricted starting January 2027, a notice sitting on zod's own npm page right now.
- Know your rotation order before you need it. Credential first, then the published artifact, then anything downstream that consumed it. Writing that order down today is cheaper than inventing it mid-incident.
None of that is exotic. It's the same category of lesson as our Supabase RLS exposure writeup: 16,326 databases didn't leak because of clever code, they leaked because of a default nobody checked. This is that same failure mode one layer down the stack, in your dependency tree instead of your schema. If you haven't run a dependency and secrets pass on your own project recently, our pre-launch security checklist for vibe-coded startups walks through it in under an hour.
The honest disclosure, since you're reading this on a directory's blog
You're reading this on SaaSCity's blog, so let's be straight about what that means. SaaSCity is a gamified startup directory: a live city map where every accepted product gets its own building and listing page, with a human editorial look before anything goes up. That human-in-the-loop part isn't a throwaway line here. Automated scanning caught the zod attack before publish, a genuine win for infrastructure, but somebody still had to open issue #6658 and ask why that commit was still sitting there. The same pattern is worth keeping in any system, listings included: automated review alone misses the thing nobody thought to flag.
If you've got a product ready to go, adding the SaaSCity badge to your own site earns a dofollow backlink and a Monday launch slot, or you can skip the queue with Quick Pass for $19.99 for a review that goes live within 24 hours. Premium at $39.99 adds a written launch post with three dofollow links if you want more than the listing itself. Our Ahrefs DR sat between 47 and 56 at the last refresh, for anyone keeping score on that kind of thing.
Do this before your next deploy, not after
The honest takeaway here is not that zod is dangerous. It isn't, and the maintainer's response was fast and genuinely improved the project's publishing security. The real takeaway is that the only reason this didn't become this year's ChainDrop is that automated scanning happened to be watching at exactly the moment it mattered. That's luck with good infrastructure attached, not a strategy. Go check your own lockfile, confirm npm ci is what your CI actually runs, and look at which of your dependencies still get to run arbitrary code the second someone types install. If you're about to put something in front of real users, that review belongs before launch day, not after someone else finds the issue for you.
Get your SaaS in front of founders
List your product on the SaaSCity live city map - a permanent listing, real discovery, and a backlink from a high-DR directory. Free to start; upgrade for a dofollow link and a building on the map.


