News for hackers. Stuff that matters.

colinmcd

Re:Do not install Zod 4.6.6 – might be infected on Monday October 12, 2026 @01:10PM (#50059470)
Those are autogenerated release notes that list all commits in the diff. The bad commits were reverted before publication.
Re:Do not install Zod 4.6.6 – might be infected on Monday October 12, 2026 @12:34PM (#50058900)
This is not the case. Zod's release CI was not triggerable via commits/tags by design. It used environment-scoped trusted publishing, which requires a GitHub token with elevated permissions. No releases of Zod or Standard Schema were sent to npm. Their security advisory is very misleading in this regard, which is why I tried to clarify the situation in my comment. [0]

[0] https://github.com/orgs/community/discussions/208477#discuss...

Re:Do not install Zod 4.6.6 – might be infected on Monday October 12, 2026 @11:11AM (#50057591)
Zod maintainer here. As others have noted, no malware was shipped in any release. This was three weeks ago, and it was caught very quickly. All my git and npm credentials were immediately revoked to prevent further access. There's a security writeup from the NPM team here. [0]

It seems like a git push credential, probably an SSH key, was exfiltrated from my local computer. The attacker used this to try to push malicious versions of Zod, standard-schema, and nub. The release workflows for Zod and standard schema were already hardened against this (environment-scoped trusted publishing, which requires an npm credential), and they could not be triggered via simple commits. Nub's release CI was triggered but npm flagged the malware before it went out.

All my packages have been switched over to staged trusted publishing, which requires 2FA for any release. Since I don't know exactly what else what compromised I've also done a full OS re-install and rotated all creds present on the old machine at the time of the compromise.

Ironically this likely happened due to some work on a sandbox for Nub's package manager. The attack is consistent with a primitive older variant of a Shai-Hulud-style worm. While working on the sandbox, an ersatz agent likely executed a compromised version of some package on npm in a test run. I had safeguards against this (filtering out any OSV-flagged packages, etc) but perhaps there's an undiscovered malicious package. Future work on this work will happen on an isolated VM.

It seemed shady to scrub the commits from history, and it still isn't clear to me that doing so is better from a security standpoint. But since there are concerns around git bisect, etc, I've done so anyway. v4.6.6 will lose provenance, v4.6.7 is being released.

[0] https://github.com/orgs/community/discussions/208477

You can continue using pnpm if you want. Nub is a la carte. Nub's runtime or script runner functionality will work with any package manager that uses node_modules (actually Yarn PnP is also supported out of the box).

You can also move over to Nub from pnpm incrementally if you want the performance or security wins. It has complete pnpm compatibility: flag for flag identical CLI, reads and writes from your pnpm-lock.yaml, and respects your expecting configs completely. Nub and pnpm can be used simultaneously by different teammates without lockfile churn. This is a key design goal, and its what I mean by "incrementally adoptable". But yeah `nub install` will almost certainly work in your repo out of the box. (File an issue otherwise.)

https://nubjs.com/docs/install/pnpm

Same is true for npm, yarn, and bun as well. It detects your current package manager (based on package.json#packageManager or lockfile existence) and runs in "compatibility mode" for that specific package manager.

https://nubjs.com/docs/install#config-it-reads

Exactly this. TypeScript also ended up being many users' entrypoint to JSX, decorators, etc as it became a catchall transpiler surface so respecting tsconfig is vital here (Node understandably has a policy against this). Plus there are extensionless imports, the "phantom" .js imports (pointing to .ts files), etc. List goes on. Type stripping ends up working out of the box for virtually no real world projects sadly.

https://nubjs.com/docs/runtime/typescript

Indeed, Nub intentionally introduces no Nub-specific APIs: no Nub global, no nub: prefixed built-in modules, no Nub-named config file / lockfile, no "nub" field in package.json, not even any NUB_ environment variables. Most of the stuff Bun added is better as a proper dependency imo.
Certainly. We're using subtrees now instead of submodules, lets avoids some tricky worktree/submodule interactions during local dev.

We've already pulled changes since that PR landed.

https://github.com/nubjs/nub/commit/b4abee87

EDIT: Sorry, I understand you're talking about package.json. Would be fun to try to get the Node & package mgmt teams aligned to add support for comments in the package.json. Bun tried and failed to do this (requires ecosystem coordination).

Nub could absolutely support a config file and use it to set NODE_OPTIONS or flags in the node child process. There's no reason to throw out the baby with the bathwater due to DX concerns like this. That's a key part of the concept Nub is trying to prove. (To be clear I'm quite content to conform to Node's no-config-file policy at the moment.)

I almost called it "oi" but I'm not sure anyone would have gotten the joke :P
Right now, you should use Nub on the backend if you are relying on its augmentations. If you specifically want to disable Nub's augmentations (so you have a guarantee that your app/script will "just work" with regular Node, there's a couple ways to disable it.

  NODE_COMPAT=0 nub index.ts
  nub --node index.ts

I'll investigate a `nub build` that would do the transpilation upfront and properly chunk/bundle a prod build. It's a good idea. But yes, Nub's overhead (both time and space) is generally negligible relative to Node itself.

Re: added attack surface: the most obvious one is that Nub loads .env files (same as Bun/Next/Vite) so be aware of that. All of Node's permission flags are passed through as well. I won't claim there's no additional attack surface, but it doesn't have much surface area, just a Rust wrapper that spawns `node` ultimately.

We use this to register our preload purely for performance reasons. In this and many other cases CommonJS is still faster than ESM. Using --require is about 0.5ms overhead vs 4.6ms for --import (on my M1 Macbook Pro).

Relatedly Node.js recently (2025) introduced a synchronous version of its resolver hook registration API (`module.registerHooks()`) specifically to improve performance over the old async `module.register()` API. It was a big unblocker for Nub. For the interested, the async API added 19ms fixed registration overhead + about 130us additional overhead per import.

Which flag Nub uses here doesn't impact userland at all, TLA is supported wherever it's supported by Node.js itself.

It entered public beta last week, but just getting on HN now.
Coming very soon!
Thanks :) Highly recommend clicking the link too!
Yep, full support on macOS, Linux, Windows. No official image yet (I'll start on this now) but you can get started with something like this.

  FROM node:26-slim
  RUN npm i -g @nubjs/nub
Works with any Node version down to 18.19 but recommend 22.15+ for best performance (that's when synchronous registerHooks was introduced[0])

[0] https://nodejs.org/api/module.html#moduleregisterhooksoption...

Cloudflare Workers is a different runtime and has its own toolchain around it. Nub could theoretically support it when executing files (spawn `wrangler dev` instead of `node` if wrangler.toml is detected or something) but really I'm focused on making the Node.js experience as good as possible.

The other pieces of the toolkit could absolutely be used: package manager, script runner, package runner. Works with anything that implements the Node module resolution algorithm (actually Yarn PnP also works out of the box...).

Colin here, creator of Nub. I’ve had the general shape of this in mind for years. Nub runs your code with stock `node`, augmented with a `--require` preload hook[0] that adds a transpiler (oxc-powered, packaged as a Node-API add-on), registers a module resolution hook[1], and injects polyfills as needed for APIs like `Worker`, `Temporal`, etc. All purely additive, your code ultimately runs using Node’s actual engine & stdlib implementations.

[0] https://nodejs.org/api/cli.html#-require-module

[1] https://nodejs.org/api/module.html#moduleregisterhooksoption...

Re:Zod 4 on Tuesday May 20, 2025 @02:55PM (#44044693)
As you allude to: your aliased "zod-next" dependency wouldn't be able to satisfy the requirements of any packages with a peer dep on Zod. But this approach has a more fundamental flaw. My goal is to let ecosystem libraries support Zod 3 and Zod 4 simultaneously. There's no reliable way to do that if they aren't in the same package.[0]

[0] https://github.com/colinhacks/zod/issues/4371

Re:Zod 4 on Tuesday May 20, 2025 @02:45PM (#44044591)
It won't typecheck, which is good in this case, because as you say that's a very bad idea :)
Re:Zod 4 on Tuesday May 20, 2025 @02:40PM (#44044539)
I understand this as a knee-jerk reaction. I didn't do this lightly.

> Perhaps publish a 4.x along with the 3.x stuff

You have some misconceptions about how npm works. Unfortunately it's less reasonable than you think. There's a single `latest` tag, and there's only one "latest" version at a time. It's expected that successive versions here will follow semver. Once I publish zod@4 I can no longer publish additional [email protected] versions. The workaround here is to publish v3 versions to a separate dist tag (zod@three) but anyone consuming that dist-tag (e.g. "zod": "three" in their package.json) loses the ability to specify a semver range.

I recommend reading the writeup[0]. I don't think you're appreciating the magnitude of the disruption a simple major version bump would have caused, or the reasons why this approach is necessary to unlock continuity for Zod's ecosystem libraries. They're quite subtle.

[0] https://github.com/colinhacks/zod/issues/4371

Re:Zod 4 on Tuesday May 20, 2025 @02:32PM (#44044474)
No, that kind of interop, especially static interop (assignability), would've been totally unworkable. Despite the length of the changelog, there are very few breaking changes to the user-facing API surface. It's mostly internal/structural changes and deprecations (most of which can be fixed with a find&replace).

Report back about that .d.ts issue. It should be far better. That kind of type explosion usually happens when TypeScript needs to infer function/method return types. Zod 4 uses isolatedDeclarations so this kind of thing shouldn't happen.

Re:Zod 4 on Tuesday May 20, 2025 @03:26AM (#44038742)
Yes! Zod now differentiates between `z.string().optional()` and `z.union([z.string(), z.undefined()])` (as in TypeScript itself). Details: https://x.com/colinhacks/status/1919291504587137496
Re:Zod 4 on Tuesday May 20, 2025 @03:24AM (#44038727)
Yep, at some indeterminate point when I gauge that there's sufficient support for Zod 4 in the ecosystem, I'll publish `[email protected]` to npm. This is detailed in the writeup[0]

[0] https://github.com/colinhacks/zod/issues/4371

Re:Zod 4 on Monday May 19, 2025 @03:14PM (#44033668)
This is not done for Zod's benefit. It's done for the benefit of libraries that depend on Zod, and the users of those libraries. If a library wants to add "incremental" support for Zod4 (that is, without dropping support for Zod 3 and publishing a new major), they need access to types (and possibly runtime code) from both libraries to do so in a sound way. I detail a number of other approaches for achieving this here[0] and why they ultimately fall short. Ultimately npm wasn't designed for this particular set of circumstances.

[0] https://github.com/colinhacks/zod/issues/4371

Older »