DEV Community

Ashraf
Ashraf

Posted on

Zod Got Backdoored. Your Lockfile Didn't Save You, Luck Did.

Zod is in roughly every TypeScript codebase you've touched. Last weekend, "Zod 4.6.6 compromised by malware?" hit the front page of Hacker News. Here's what actually happened, and why the lucky ending shouldn't make you relax.

TL;DR

  • Malicious commits were pushed to colinhacks/zod on GitHub. Same pattern hit nubjs/nub and standard-schema/standard-schema.
  • The payload was a remote loader that asks an Ethereum smart contract where to download its next stage.
  • No malicious Zod version reached npm. Per GitHub's advisory, npm's publish-time scanning held the one malicious version that was submitted (@nubjs/nub@0.9.4) before it was installable. The zod and standard-schema changes never got published.
  • Zod's own release flow (trusted publishing plus a dedicated GitHub environment) is what kept the repo compromise from becoming a registry compromise.

What the attacker did

The malicious files showed up in test-looking paths:

packages/zod/meta.test.mjs
packages/mini/meta.test.mjs
Enter fullscreen mode Exit fullscreen mode

Reported behavior of the loader, per GitHub's security advisory:

  1. Use preinstall/postinstall lifecycle hooks to bootstrap.
  2. Query Ethereum RPC endpoints to read a smart contract.
  3. Pull a delivery location out of that contract.
  4. Download and execute remote JavaScript.
  5. Clean up after itself.

Step 2 is the clever part. The payload lives off-repo and off-registry, and the attacker can swap it by updating a contract. Reviewing the package contents tells you nothing about what the loader fetches tomorrow. You can't takedown a smart contract.

Indicators from the advisory:

SHA-256: 5ed518aaff5aebc729545b8eb223ec31927cd1bfa53169517f09cdbcc9fd5a0b
Delivery domain: dsakldjklsaj33112[.]com
Enter fullscreen mode Exit fullscreen mode

Per the HN discussion, the malicious commit was reverted by the maintainer, and one commenter noted the preinstall entry was placed in the wrong spot in package.json (root, not under scripts), which may have kept it from running at all. Treat that as a happy accident, not a defense. (Dates in the HN thread are the commenter's account; check the GitHub issue #6658 and the advisory for the authoritative timeline.)

Why this should still scare you

We got lucky in layers:

  • The attacker was sloppy with the manifest.
  • The maintainer had locked down publishing.
  • npm's scanner caught the nub version.

Remove any one of those and this is a different post. And this isn't a one-off. Earlier this year @tanstack/zod-adapter shipped malware (v1.166.12 and v1.166.15) after an npm account takeover, tied to the "Mini Shai-Hulud" campaign that GitLab's advisory says hit 314 packages. In 2025 a phished maintainer gave attackers chalk and debug. The pattern: someone's credential is the weak link, and your npm install runs their code with your privileges.

An install script runs as you. That means ~/.ssh, ~/.aws/credentials, ~/.npmrc tokens, and every env var in your shell.

Do this today

1. Turn off dependency install scripts by default.

# ~/.npmrc (or project .npmrc)
ignore-scripts=true
Enter fullscreen mode Exit fullscreen mode

Then rebuild only what needs it (sharp, bcrypt, sqlite3):

npm ci --ignore-scripts
npm rebuild sharp
Enter fullscreen mode Exit fullscreen mode

GitHub has said npm will move to opt-in script execution for dependencies. Upgrade to npm 11.16.0+ to prepare. Don't wait for the default to flip.

Caveat: ignore-scripts isn't a silver bullet. A package can still register a malicious bin entry (the Bitwarden incident did exactly that), which runs when the command is invoked.

2. Add a cooldown. Fresh versions are where compromises live. Pair ignore-scripts with a minimum release age so you don't install something published 20 minutes ago.

3. Make new install scripts a CI failure. Diff package-lock.json for new "hasInstallScript": true entries and require human review.

4. Lock your own publishing, if you maintain anything. Zod's setup is the template: trusted publishing (OIDC, no long-lived npm tokens), a dedicated GitHub environment with required approval, and 2FA that can't be bypassed from CI.

5. Pin and verify. Commit lockfiles, use npm ci, and don't npm update blind on a Friday.

6. Quick check right now:

grep -rn "dsakldjklsaj33112" node_modules package-lock.json 2>/dev/null
grep -n '"hasInstallScript": true' package-lock.json | wc -l
Enter fullscreen mode Exit fullscreen mode

If the first command returns anything, rotate every credential on that machine, then investigate.

The takeaway

Zod's maintainers did the right things and it worked. That's the lesson: the controls that stopped this (scoped publishing, scanning, no install-time code) are all things you can adopt. The attackers are already using blockchains for command-and-control. Your .npmrc is one line.

Add it before lunch.

Sources: GitHub Community Discussion #208477 (security advisory), the Hacker News thread (item 50052814), GitLab advisory GMS-2026-451, zod issue #6658.

Top comments (0)