GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Security patches (stable/2.1.x) by renovate[bot] · Pull Request #444 · DefGuard/proxy

Conversation

@renovate

@renovate renovate Bot commented Nov 4, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence Type Update
qs 6.16.0 → 6.16.1 age confidence dependencies patch
tokio (source) 1.53.1 → 1.53.3 age confidence dev-dependencies patch
tokio (source) 1.53.1 → 1.53.3 age confidence dependencies patch
zod (source) 4.4.3 → 4.6.6 age confidence dependencies minor

qs : qs.stringify with arrayFormat 'comma' throws an uncaught TypeError on array elements carrying a non-callable own toString (DoS)

CVE-2026-110816 / GHSA-6pq8-jhm2-2vcw

More information

Details

Summary

qs.stringify(obj, { arrayFormat: 'comma' }) throws an uncaught TypeError: Cannot convert object to primitive value when an array in obj contains an element that carries an own, non-callable toString property (for example { a: [ { toString: 'x' } ] }). A remote, unauthenticated attacker who can influence an object that the application later re-serializes with the comma array format triggers the throw. In a service that lacks a synchronous error boundary around the serialization call, the uncaught exception crashes the worker process, producing a denial of service.

Root Cause

When arrayFormat: 'comma' is selected, the comma branch collapses the whole array into a single value via Array.prototype.join, or, under encodeValuesOnly: true, maps each element through the value encoder before joining. Both paths perform an implicit string coercion (ToPrimitive with hint "string") on each element. For an object whose own toString is a non-callable value, coercion skips the shadowed non-callable toString, falls through to Object.prototype.valueOf (which returns the object rather than a primitive), and throws. The comma branch runs this coercion before the per-element null and skip handling in the main loop, so nothing downstream can neutralize the hostile element, and the throw is never caught inside the library.

lib/stringify.js  (comma-array branch)
  line 152-154: obj = utils.maybeMap(obj, function (v) { return v == null ? v : encoder(v); });  // encodeValuesOnly path
  line 156:     objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];   // default comma path
lib/utils.js
  line 292:     string = String(str);   // value encoder coercion reached via the encodeValuesOnly path
Impact

Prerequisites: the application calls qs.stringify with arrayFormat: 'comma' (a documented, supported option) on an object whose shape can be influenced by attacker input. Attacker-controllable sources include qs.parse(input, { allowPrototypes: true }), which retains a toString segment (Express 4's default query parser, Express 5's extended query parser, and body-parser's extended urlencoded parser all set it), qs.parse(input, { plainObjects: true }), whose null-prototype elements throw even without a toString key (eg, a[][b]=c), and JSON.parse of a request body, which needs no qs parse option at all. No authentication or user interaction is required. Demonstrated capability: a single request forces an uncaught synchronous TypeError in the serialization call. In a naive single-process Node service with no synchronous error boundary, this crashes the process and takes the service offline (availability impact). Under a framework such as Express that traps synchronous handler throws, the plausible outcome is a per-request 500 rather than a full outage; that lower-impact case is noted but not the worst credible supported deployment. There is no confidentiality or integrity impact, no memory unsafety, and no prototype pollution.

Proof of Concept

Install qs at the affected version (for example 6.16.0) and run the following script with Node.js. No network setup is required; it demonstrates the throw in isolation.

var qs = require('qs');

// Source A: qs.parse with allowPrototypes retains the hostile toString segment
qs.stringify(qs.parse('a[0][toString]=x', { allowPrototypes: true }), { arrayFormat: 'comma' });

// Source B: a JSON request body, no qs parse option needed
qs.stringify(JSON.parse('{"a":[{"toString":"x"}]}'), { arrayFormat: 'comma' });

// Also affected: the encodeValuesOnly sub-path
qs.stringify(JSON.parse('{"a":[{"toString":"x"}]}'), { arrayFormat: 'comma', encodeValuesOnly: true });
A default-join (allowPrototypes) => THREW: TypeError: Cannot convert object to primitive value
B default-join (JSON.parse) => THREW: TypeError: Cannot convert object to primitive value
C encodeValuesOnly path => THREW: TypeError: Cannot convert object to primitive value
N2 callable toString => NO THROW; output="a=z"
N3 isBuffer vector (CVE-2026-82417) => NO THROW; output="a%5Bconstructor%5D%5BisBuffer%5D=x"
qs version: 6.16.0
Attack Chain
  1. Exposure: A remote, unauthenticated request reaches an endpoint whose handler eventually serializes attacker-influenced data with qs.stringify(obj, { arrayFormat: 'comma' }).
  2. Control: The attacker supplies data that becomes { a: [ { toString: <non-callable value> } ] }, either via a[0][toString]=x parsed with allowPrototypes, or via a JSON body {"a":[{"toString":"x"}]}.
  3. Path: In stringify, the primitive/buffer guard at lib/stringify.js:134 is false for an array, so control reaches the comma branch at lib/stringify.js:149.
  4. Guard: On the encodeValuesOnly path, the closure v == null ? v : encoder(v) at lib/stringify.js:152-154 passes only null/undefined through; a non-null object still reaches the encoder. The default path skips that closure entirely and reaches obj.join(',') at lib/stringify.js:156 with no guard at all.
  5. Primitive: The element is coerced to a string (Array.prototype.join, or String(str) at lib/utils.js:292). ToPrimitive skips the non-callable own toString, valueOf returns the object, and a TypeError is thrown.
  6. Result: The uncaught synchronous exception propagates out of the serialization call, failing the request and, in a service without a synchronous error boundary, crashing the process (denial of service).
Bypass Evidence

This is a different element type on the code path CVE-2026-8723 (GHSA-q8mj-m7cp-5q26) patched, plus the default comma path, which that fix never covered. CVE-2026-8723 addressed a TypeError crash on null/undefined entries in the comma plus encodeValuesOnly path. Its fix (commit 21f80b3) wraps the encoder in function (v) { return v == null ? v : encoder(v); }, so it only passes null and undefined through. A non-null element with a hostile own toString fails the v == null test, so the encoder still runs and still throws; the encodeValuesOnly case (case C above) throws on the exact fixed code. The default comma path (encodeValuesOnly unset, the library default) never entered that fix at all: git log -S "obj.join(','" -- lib/stringify.js shows no security fix has ever touched the obj.join(',') line, and cases A and B throw with encodeValuesOnly unset. The related isBuffer hardening for CVE-2026-82417 (commit e83d321) is a different sink at the lib/stringify.js:134 guard, which is never entered for the array element; the negative control {a:{constructor:{isBuffer:'x'}}} serializes cleanly. Disproof attempts that failed: a valueOf-only object does not throw (it serializes to [object Object]), confirming the trigger is an element with no callable toString (an own non-callable toString, or a null-prototype object with no toString at all, as qs.parse produces under plainObjects); nested arrays ({"a":[[{"toString":"x"}]]}) throw the same way; a callable toString serializes cleanly; and the default (non-comma) array format serializes cleanly, confirming the comma format is required. All positive and negative cases were reproduced on the exact published 6.16.0 artifact.

Affected Versions
  • Ecosystem: npm
  • Package: qs
  • Confirmed affected range: >= 6.7.0, <= 6.16.0
  • Latest release checked: 6.16.0 (npm latest; git tag v6.16.0)
  • Fix status: fixed in 6.16.1 (a952e75)

The arrayFormat: 'comma' join sink was introduced in qs 6.7.0, so 6.7.0 is the lower boundary; releases before 6.7.0 lack the sink. No released version fixes this vector, and the latest published release (6.16.0) reproduces the throw, so the closed upper bound <= 6.16.0 covers every affected release. The encodeValuesOnly sub-path was added later, but the default comma-join vector already affects 6.7.0, so 6.7.0 governs the lower boundary.

Suggested Fix

The comma array serialization must not allow an implicit string coercion of an array element to throw an uncaught exception. Coercion of each comma element should be performed defensively (for example by wrapping the join and per-element encode so that a coercion failure is caught and handled), and this protection must cover both the default obj.join(',') path and the encodeValuesOnly encoder path, not only the previously patched null/undefined case. As an interim mitigation without a code change, applications can avoid arrayFormat: 'comma' when serializing data derived from untrusted input, sanitize parsed objects (avoid allowPrototypes/plainObjects on untrusted input and strip unexpected own properties), or wrap qs.stringify calls in a synchronous error boundary so a coercion failure cannot crash the process.

Reported by zx (GitHub: @​manus-pi).


Independent report by @​Lucidsec

The following was reported separately, after this advisory, and is merged here as a duplicate.

Summary

In project qs, in versions >= 6.7.0, <= 6.16.0 (every version that supports arrayFormat: 'comma'), qs.stringify with arrayFormat: 'comma' can throw an uncaught TypeError: Cannot convert object to primitive value when an array element has a non-callable own toString. It is fixed in 6.16.1. Related: CWE-248 and CWE-704.

Technical Details

qs.stringify with arrayFormat: 'comma' joins an array at lib/stringify.js:156 via obj.join(','). An element whose own toString is not callable makes Array.prototype.join throw. There is no type check and no catch on that path.

A second path exists with encodeValuesOnly: at lib/stringify.js:151-154, maybeMap only skips v == null, then lib/utils.js:292 does String(str) and throws the same TypeError (this second site exists since v6.11.1; before that, the same input throws at join).

Who can trigger: someone who can shape an array element passed to stringify. The host must use non-default arrayFormat: 'comma'. Default arrayFormat indices does not fold arrays and is not affected. A parse-then-stringify path also needs plainObjects: true (null-prototype elements from lib/parse.js:200) or allowPrototypes: true (which Express 4's default query parser, Express 5's extended query parser, and body-parser's extended urlencoded parser all set). Default qs.parse('a[0][toString]=x') returns {} because the prototype guard drops toString. Direct JSON handed to stringify can supply {"a":[{"toString":"x"}]}.

Cause: the comma fold at lib/stringify.js:149-156 collapses the array into a single value before the main loop at lib/stringify.js:172-212, so skipNulls and strictNullHandling only ever see the joined value, never individual elements. isNonNullishPrimitive / isBuffer at lib/stringify.js:134 inspect the whole value, not each element. A normal object uses Object.prototype.toString and becomes [object Object]. An own non-callable toString is skipped, valueOf returns the object, and join throws. (The skipped TODO test at test/stringify.js:684-689 uses a plain { b: 1 } element, which does not throw; it covers the separate question of what comma format should output for object elements, not this crash.) Earlier comma crash fixes did not cover object elements.

Impact

One small input makes that serialization path throw a synchronous, catchable TypeError. Behind a framework error boundary (eg, a synchronous Express handler), the request fails with a 500 and the process survives. In a host with no error boundary (eg, a bare http.createServer handler), the uncaught exception exits the process (the client sees ECONNRESET). Availability only.

Proof of Concept
Show the proof of concept

Checked on the latest commit that was checked: 07b1d4d82c8f9301c105ea4b94fa2302cfd6e8b4. Still present. Demonstrated.

  • Payload {"a":[{"toString":"x"}]} throws at lib/stringify.js:156.
  • Baseline {"a":["x","y"]} returns 200 with body a=x%2Cy.
  • A plain object element stringifies to a=%5Bobject%20Object%5D and does not throw.
  • encodeValuesOnly: true throws via utils.js:292.
  • plainObjects parse plus comma throws.
Suggested Fix

Before join, map elements with the existing primitive check; skip or replace non-primitives so join cannot throw. On the encodeValuesOnly path, do not send non-primitives into String(). Optionally guard lib/utils.js:292. Add new regression tests.

Severity

  • CVSS Score: 6.3 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N

References

  • https://github.com/ljharb/qs/security/advisories/GHSA-6pq8-jhm2-2vcw
  • https://nvd.nist.gov/vuln/detail/CVE-2026-110816
  • https://github.com/ljharb/qs/commit/a952e7519338ef0cfb25694a2988848f93333727
  • https://github.com/ljharb/qs
  • https://github.com/ljharb/qs/releases/tag/v6.16.1

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


AsyncFd::new and related constructors violate I/O safety

RUSTSEC-2026-0386

More information

Details

tokio::io::unix::AsyncFd provides safe constructors (new, with_interest, try_new, and try_with_interest) that accept any T: AsRawFd rather than requiring proof of ownership or validity of the file descriptor. Because RawFd implements AsRawFd, safe code can construct an AsyncFd from an arbitrary or closed file descriptor, or close/replace the underlying file descriptor while the AsyncFd is still alive.

This violates I/O safety in two ways:

  1. AsyncFd<T> implements AsFd for all T: AsRawFd by calling BorrowedFd::borrow_raw(self.as_raw_fd()), allowing safe code to obtain an arbitrary BorrowedFd<'_> without owning the file descriptor.
  2. Tokio's I/O driver stores a pointer to an internal allocation in the epoll/kqueue user data for registered file descriptors. If the underlying file descriptor is duplicated and then closed while an AsyncFd is alive, and the OS reuses that file descriptor number for a another IO resource on the same runtime, dropping the first AsyncFd deregisters the reused file descriptor number and frees the first while the original file description remains registered in epoll, leading to a use-after-free when a readiness event fires.

Starting in Tokio 1.51.6, 1.53.3, and 1.54.0, AsyncFd::new, AsyncFd::with_interest, AsyncFd::try_new, and AsyncFd::try_with_interest are deprecated in favor of the unsafe constructors AsyncFd::register and AsyncFd::register_with_interest. Callers must ensure that the file descriptor remains valid, open, and unchanged for the lifetime of the AsyncFd.

Severity

Unknown

References

  • https://crates.io/crates/tokio
  • https://rustsec.org/advisories/RUSTSEC-2026-0386.html
  • https://github.com/tokio-rs/tokio/pull/8587
  • https://github.com/tokio-rs/tokio/issues/8538
  • https://github.com/tokio-rs/tokio/issues/8539

This data is provided by OSV and the Rust Advisory Database (CC0 1.0).


Zod can exhaust memory while validating oversized constrained collections

CVE-2023-54404 / GHSA-mrhx-c7xc-m3w5

More information

Details

Zod schema-validation library through 4.6.5 contains an uncontrolled resource consumption vulnerability that allows attackers to exhaust memory by submitting a large array to an application using an array schema without a length constraint. Attackers can exploit the handleArrayResult parse logic in $ZodArray, which accumulates every validation issue for each failing element with no cap or early termination, causing the process to allocate excessive issue objects and crash due to out-of-memory conditions.

Severity

  • CVSS Score: 8.2 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

  • https://nvd.nist.gov/vuln/detail/CVE-2023-54404
  • https://github.com/colinhacks/zod/issues/1872
  • https://github.com/colinhacks/zod/pull/6475
  • https://github.com/colinhacks/zod/pull/6652
  • https://github.com/colinhacks/zod/commit/a0ebf970f2d752e5f58527cd2942ff48be4f46bd
  • https://github.com/colinhacks/zod
  • https://www.vulncheck.com/advisories/zod-uncontrolled-resource-consumption-via-array-validation

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


Release Notes

ljharb/qs (qs)

v6.16.1

Compare Source

  • [Fix] parse: do not mutate an object input
  • [Fix] stringify: stop re-encoding structural separator dots when encodeDotInKeys is set (#​564)
  • [Fix] stringify: arrayFormat: 'comma': pass the full encoder arguments with encodeValuesOnly
  • [Fix] utils: encode + as %2B in iso-8859-1 mode so values round-trip (#​565)
  • [Fix] parse: only convert top-level dots to brackets when allowDots is on (#​621)
  • [Fix] stringify: ignore inherited array format names (#​622)
  • [Fix] stringify: arrayFormat: 'comma': do not throw on elements with no callable toString
  • [Fix] utils: merge: seed an overflow container's index from its own keys, not just the source
  • [Fix] utils: merge: keep a strictMerge conflict container's index and arrayLimit correct
  • [Fix] parse: only treat [] as array syntax when it ends the key
  • [Perf] decode: skip decoders when there is no percent sign (#​583)
  • [Robustness] utils: merge: apply arrayLimit to the two-element array built for colliding values
  • [Robustness] parse: bound the nesting added by repeated strictMerge conflicts (credit @​Yeaseen)
  • [Docs] document that qs and flat parsers disagree about parameter names
  • [Docs] clarify that throwOnLimitExceeded limits each array, not the total number of values
  • [Docs] clarify 6.14.1/6.15.2 parse bracket behavior change (#​567, #​568)
  • [meta] fix changelog indents
  • [actions] set least-privilege cache-mode
  • [Tests] utils: decode: add missing cases
  • [Tests] parse: characterize the ]= split when an = precedes any bracket group (#​620)
  • [Tests] parse: allowDots: cover stray close brackets before a dot
  • [Dev Deps] update @ljharb/eslint-config, eslint, tape
tokio-rs/tokio (tokio)

v1.53.3: Tokio v1.53.3

Compare Source

1.53.3 (October 13th, 2026)

Added
  • io: add AsyncFd::register and AsyncFd::register_with_interest (#​8587)
Changed
  • io: deprecate AsyncFd::{new, with_interest, try_new, try_with_interest} (#​8587)
Fixed
  • io: wake and drop DuplexStream wakers outside the lock (#​8618)
  • macros: avoid unreachable_code in join! and try_join! for Infallible (#​8518)
  • runtime: fix paused time auto-advancing when I/O wakes a task on current_thread (#​8595)
  • sync: wake waiters outside the lock when closing a semaphore (#​8529)
  • test: fix several flaky tests (#​8196, #​8427, #​8609)
Fixed (unstable)
  • rt: keep io_uring op alive when submission fails after push (#​8185)

v1.53.2: Tokio v1.53.2

Compare Source

1.53.2 (October 3rd, 2026)

Fixed
  • fs: handle integer overflow in buffered relative seek (#​8574)
  • io: revert "always cleanup AsyncFd registration list on deregister" (#​8540)
  • process: unregister Windows wait before closing child handle (#​8564)
  • rt: drop blocking pool mutex before shutting down rejected task (#​8562)
  • sync: fix mpsc index wraparound in block reclamation (#​8546)
  • sync: forget mpsc Permit before sending value (#​8560)
  • sync: validate MAX_PERMITS in Semaphore::acquire (#​8548)
  • sync: wake broadcast Sender::closed outside mutex (#​8558)
  • task: drop replaced waker outside lock in JoinSet (#​8554)
  • time: drop timer lock before dropping waker in clear_entry (#​8552)
  • time: expire timers directly on shutdown without rotating wheel (#​8570)
Fixed (unstable)
  • fs: clamp io_uring read length to u32::MAX (#​8572)
  • rt: ignore current_thread task dumps from other runtimes (#​8544)
  • rt: preserve io_uring context if a completion waker panics (#​8566)
  • sync: fix semaphore use-after-free and permit leak on tracing panic (#​8542)
  • taskdump: restore deferred leaf wakes during capture (#​8445)
  • time: drop stored waker when cancelling alt timer entry (#​8550)
colinhacks/zod (zod)

v4.6.6

Compare Source

Six malicious commits pushed to main on September 21 with a compromised git credential were reverted the same day in #​6620 and have since been removed from the history of main. None were ever published to npm. The npm provenance for 4.6.6 names 284243f, the pre-rewrite copy of 9c21983 with identical contents.

Commits:

  • 0cd3af5 docs: load the en locale directly in Zod Mini
  • 9d5e60f docs: import locales from zod/locales, not z.locales
  • 34a626f docs: tighten the z.locales tree-shaking note
  • 74304ce release: stage npm versions instead of publishing them (#​6622)
  • b2ef983 docs: add Code for Japan to bronze sponsors
  • 8fb2faf docs: crop the Code for Japan logo and list it first in bronze
  • bacad51 docs: square the Code for Japan logo to match the other bronze tiles
  • c42596b release: hold the run on the release-approval environment until the staged versions are approved (#​6638)
  • a2ba4a6 fix(types): declare record output and input as members, not base type arguments (#​6645)
  • a0ebf97 fix(v4): check array, set and map size bounds before parsing elements (#​6652)
  • 529f78d 4.6.6
  • 9c21983 ci: stage at verbose so a failed OIDC token exchange says why

v4.6.5

Compare Source

Commits:

  • d2b135c docs: add the 4.6.x patch highlights to the 4.6 post
  • f1448f7 docs: fold the 4.6.x patch highlights into the 4.6 post's own sections
  • de65a5c docs: lead the properties section with the check and add a Zod Mini tab (#​6598)
  • 56222cd feat(instanceof): key the .properties() shape off the instance type (#​6600)
  • ca0229a Revert "feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed weekly by CI (#​6595)"
  • cc4cd4e Revert "Revert "feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed weekly by CI (#​6595)""
  • 0f3f5ee 4.6.5
  • 59bbc03 chore: re-pin the integration peers to the workspace zod after the 4.6.5 bump

v4.6.4

Compare Source

A patch on top of 4.6.3.

  • d6bc1e30 feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed weekly by CI (#​6595)
  • ad32d751 perf: z.url() rejects an invalid URL with URL.canParse() instead of a throwing constructor, about 50x faster; fewer allocations on the validation path (#​6588)
  • 2bb08717 chore: re-pin the integration peers to the workspace zod after the 4.6.4 bump
  • f6e1701a chore(deps): bump next to 15.5.25 and vite to 7.3.6 (#​6153)

v4.6.3

Compare Source

A patch on top of 4.6.2.

  • 413cce9a fix(v4): make z.properties() a check again (#​6594) — removes the standalone z.properties() schema from 4.6.0; z.instanceof().properties() and .check(...z.properties()) are unchanged
  • 75d63ee1 docs: show only the .properties() method form in the 4.6 post
  • 46da9572 docs: match the error-message examples to what the parsers emit

v4.6.2

Compare Source

A patch on top of 4.6.1.

  • 9446b5cc fix: preserve undefined prefault outputs and object keys (#​6587) — closes #​6585
  • 0c483c58 docs: the Zod 4.6 announcement post (#​6546)
  • a00c3f34 docs: use Trigger.dev's brand-kit lockups for the platinum card

v4.6.1

Compare Source

A patch on top of 4.6.0.

  • b12aa523 fix: preserve unique tags with defaulted discriminators (#​6582) — closes #​6577
  • dd9c36fa fix(v4): defer recursive object index inference (#​6580)
  • 3b154992 feat(lang): add Tajik (tg) locale (#​6581) by @​ismoil77
  • 2efa8b80 ci: give the npm wait a real budget and drop the back-publish path (#​6583)

v4.6.0

Compare Source

Zod 4.6 is now available.

npm install zod@latest

At a glance:

  • .validate() — checks input validity without building a result (up to 35x faster than .safeParse().success on a compiled schema)
  • z.instanceof().properties() — validates properties of an instance
  • fromJSONSchema() — enforces six validation keywords it used to ignore
  • z.iban() — electronic-format IBAN plus mod-97 checksum
  • z.withParser() — installs a parser generated elsewhere, for environments without new Function
  • Faster CommonJS — drops the getter on every export (~3x faster z.validate() under require)
  • Memory retention in recursive schemas — releases the parsed input, fixing a 4.5 out-of-memory regression
  • @zod/mini — Zod Mini as a standalone package, versioned in lockstep with zod since 4.5

.validate()

Standalone boolean validation, in Zod, Zod Mini, and Zod Core. It answers "is this input valid?" without constructing a ZodError, which makes rejection cheap. The return type is a guard on the schema's input type.

z.validate(z.string(), "hi"); // true
z.validate(z.string(), 42);   // false

It is a method on Zod Classic schemas too. (#​6547)

const Player = z.object({
  username: z.string(),
  xp: z.number(),
});

if (Player.validate(data)) {
  data.username; // narrowed
}

In conjunction with z.compile(), this can be up to 35x faster than .safeParse().success on invalid input.

Time per call on invalid input, schemas compiled with z.compile(), safeParse().success as a gray bar with .validate() as a blue bar inside it: a union of 3 objects 28 ns (34.9x faster), an array of 10 strings 23 ns (24.6x), a 3-element tuple 28 ns (16.1x), a 5-key object 21 ns (16.3x), a discriminated union of 3 21 ns (15.7x), z.number() 19 ns (13.8x), z.string() 19 ns (13.2x), z.boolean() 19 ns (13.7x); up to 34.9x faster

Time per call on invalid input, compiled with z.compile() — lower is better (benchmark)

Uncompiled schemas

Without compilation it is up to 5.9x faster. The saving is the result object: .safeParse() allocates one with an accessor pair on every call, and .validate() allocates nothing.

Time per call on invalid input, plain schemas, safeParse().success as a gray bar with .validate() as a blue bar inside it: a union of 3 objects 659 ns (1.5x faster), an array of 10 strings 211 ns (2.3x), a 3-element tuple 166 ns (2.5x), a discriminated union of 3 93 ns (3.3x), a 5-key object 75 ns (3.9x), z.number() 47 ns (5.3x), z.string() 42 ns (5.8x), z.boolean() 42 ns (5.9x); up to 5.9x faster

Time per call on invalid input, plain schemas — lower is better (benchmark)

Both charts measure the failure path. The key feature of .validate() is that it can short-circuit on the first issue it encounters, instead of aggregating a full ZodIssue[] array.

[!NOTE]
Async refinements are covered by .validateAsync().

z.properties()

A new API for validating specific properties of an object. Unlike z.object() it validates in-place, so it plays nice with class instances. (#​6536)

const responseLike = z.properties({ status: z.number().min(200).max(299) });

responseLike.parse(new Response("ok", { status: 200 }));  // ✅ a real Response
responseLike.parse({ status: 204 });                      // ✅ a plain object

A corresponding .properties() method has been added to ZodInstanceOf.

Zod

const okResponse = z.instanceof(Response).properties({
  ok: z.literal(true),
  status: z.number().min(200).max(299),
});

Zod Mini

const okResponse = z.instanceof(Response).check(...z.properties({
  ok: z.literal(true),
  status: z.number().check(z.minimum(200), z.maximum(299)),
}));

The input comes back untouched, so the prototype survives and the methods still work. That is the part z.object() cannot do: it would hand back a plain object and the Response would be gone.

const res = await fetch("/api/user");

okResponse.parse(res) === res; // ✅ true

fromJSONSchema()

Six additional JSON Schema keywords are now supported in z.fromJSONSchema(). (#​6535)

  • minProperties / maxProperties
  • uniqueItems
  • contains
  • minContains / maxContains
const schema = z.fromJSONSchema({
  type: "object",
  minProperties: 2,      // also maxProperties
});

schema.parse({ a: 1 });        // ❌ too few properties
schema.parse({ a: 1, b: 2 });  // ✅

Both property bounds count the input's own keys. Array uniqueness is structural, so [{ a: 1 }, { a: 1 }] is a duplicate.

z.fromJSONSchema({ type: "array", uniqueItems: true }).parse([{ a: 1 }, { a: 1 }]); // ❌

z.fromJSONSchema({
  type: "array",
  contains: { type: "number" },   // also minContains and maxContains
  minContains: 2,
}).parse(["a", 2]);               // ❌ only one number

z.iban()

A new string format: an IBAN in electronic format, with a valid ISO 7064 MOD 97-10 checksum. (#​6571)

z.iban().parse("DE89370400440532013000"); // ✅
z.iban().parse("DE89370400440532013001"); // ❌ bad checksum

z.withParser()

z.compile() builds its parser with new Function, which a strict Content Security Policy blocks. z.withParser() is that installer on its own: it takes a parser generated somewhere else, at build time or by a native compiler, and installs it under the same contract. (#​6575)

const Player = z.object({ username: z.string(), xp: z.number() });

// isPlayer is a type guard your build step generated
const Fast = z.withParser(Player, (input) =>
  isPlayer(input) ? { username: input.username, xp: input.xp } : z.INVALID
);

The supplied parser owns the whole result, so it has to return what the schema would have returned. This one rebuilds the object rather than handing back its input, because z.object() strips unknown keys. Returning z.INVALID hands the input to the runtime, which stays the only source of ZodErrors.

Faster CommonJS

TypeScript compiles a re-export to a getter, and 252 of the 255 exports on Zod 4.5's CommonJS entrypoint were getters. V8 could not see a constant callee behind one, so it could not inline the call. The 4.6 build emits plain properties and freezes the exports object. On a compiled schema, z.validate() under require is about 3x faster than it was in Zod 4.5. (#​6564)

const { z } = require("zod");
const CompiledPlayer = z.compile(Player);

z.validate(CompiledPlayer, data); // ~3x faster than Zod 4.5

Only calls through the namespace were affected. A method call like Player.safeParse(data) never reads the exports object, and the ESM build is unchanged.

Memory retention in recursive schemas

A recursive schema held the input and output of its last parse until the next parse replaced it, so one long-lived schema pinned every object it had touched. Zod 4.4 released that input and Zod 4.5 did not, which surfaced as an out-of-memory failure on a repository-wide lint run. The parse state is weak throughout now: one parse of a 29k-node tree retains 2.2 MB where it used to retain 10.1 MB, and recursive parses give up about 6% for it. (#​6572)

const Category = z.object({
  name: z.string(),
  get children() {
    return z.array(Category);
  },
});

Bug fixes

⚠️ Error maps run on the first read of error

Because safeParse() now builds its error lazily, error maps — global, locale, and per-schema error — run when result.error is first read, not at parse time. Code that swaps z.config() between the parse and the read gets the newer configuration. (#​6519)

const result = schema.safeParse(12);
z.config(z.locales.fr());
result.error.issues[0].message; // French in 4.6, English in 4.5

An error map with a side effect never runs if nothing reads the error. Throwing parses are unaffected — .parse() builds and throws its error immediately, never takes the lazy path, and its stack still points at your call site.

⚠️ z.emoji() rejects component-only strings

Unicode's Emoji_Component property covers the pieces that attach to an emoji, so z.emoji() accepted "123", "#", "*", and a lone zero-width joiner, variation selector, or skin tone modifier. The pattern now requires at least one pictograph, regional indicator, or keycap. (#​6532)

z.emoji().parse("😀");   // ✅
z.emoji().parse("1️⃣");   // ✅ the keycap is the anchor
z.emoji().parse("123");  // ❌ was accepted in 4.5

Flags, subdivision flags, skin-tone-modified emoji, and ZWJ sequences are unchanged. Closes #​6515.

⚠️ Numeric enum options no longer include the reverse mappings

A numeric TypeScript enum also carries its reverse mapping (0 to "UK") at runtime. The parser already ignored those keys, but .options was read straight off the enum object, so a three-member enum listed six values and three of them failed to parse. (#​6542)

enum Country { UK, Germany, France }

z.enum(Country).options; // 4.5: ["UK", "Germany", "France", 0, 1, 2] — 4.6: [0, 1, 2]
⚠️ base64 patterns

The runtime patterns for z.base64() and z.base64url() are the character sets, with length and padding enforced in code, so a multi-megabyte string can no longer overflow the regex stack through a composed schema. The JSON Schema output still emits the exact block forms, so z.toJSONSchema() is unchanged. (#​6534, #​6527)

Composing z.base64() into a template literal now checks the alphabet but not the length, which is how z.creditCard() already behaves there. The exported z.regexes.base64url is now the length-aware form, so it overflows on a multi-megabyte input the same way z.regexes.base64 does.

⚠️ The email pattern dropped its lookaheads

z.email() opened with two lookaheads, and the second scanned the whole string before the match began. Both are gone, and the rule they enforced — no empty segment in the local part — is expressed structurally instead, so z.email() accepts and rejects exactly what it did before. Valid addresses validate roughly twice as fast. (#​6573)

The pattern string is user-visible, and every copy of it changes: z.regexes.email, which has no capture groups now — neither of the two it used to expose held a usable value; issue.pattern on a failed z.email(); and the pattern that z.toJSONSchema() emits, which no longer carr

[!IMPORTANT]
✂ PR body was truncated to here.


Configuration

📅 Schedule: (in timezone Europe/Warsaw)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@renovate renovate Bot added dependencies Pull requests that update a dependency file renovate security labels Nov 4, 2026
@renovate renovate Bot changed the title Security patches (stable/2.1.x) Security patches (stable/2.1.x) - autoclosed Nov 9, 2026
@renovate renovate Bot closed this Nov 9, 2026
@renovate
renovate Bot deleted the renovate/stable/2.1.x-security-patches branch November 9, 2026 05:37
@renovate
Update security patches [SECURITY]
89bbb3d
@renovate renovate Bot changed the title Security patches (stable/2.1.x) - autoclosed Security patches (stable/2.1.x) Nov 9, 2026
@renovate renovate Bot reopened this Nov 9, 2026
@renovate
renovate Bot force-pushed the renovate/stable/2.1.x-security-patches branch 2 times, most recently from 1f79fa7 to 89bbb3d Compare November 9, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

dependencies Pull requests that update a dependency file renovate security

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

0 participants

Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.