Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
MEDIUM severity

@orpc/zod: Prototype injection in smart coercionGHSA-gcgf-fh7c-8gf2

MEDIUMFix: middleapi/orpc#1727

GHSA-gcgf-fh7c-8gf2 is a medium-severity (CVSS 6.5) CWE-915 vulnerability in @orpc/zod. A fix is available for @orpc/zod — see the affected versions and patch details below.

Also known asCVE-2026-103918
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 24, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for GHSA-gcgf-fh7c-8gf2.

EPSS Exploitation Probability

via FIRST.org ↗
0.2%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs14th percentile — riskier than 14% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

GHSA-gcgf-fh7c-8gf2 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 394,803 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

36other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
@orpc/zodnpm
668Kdownloads / week

Description

Summary

ZodSmartCoercionPlugin and experimental_ZodSmartCoercionPlugin mishandle object keys that name Object.prototype members. Both coerce request input before validation, so any client that can reach a procedure whose input schema contains an object or a record can:

  1. replace the prototype of the coerced input object, and
  2. make a request fail with an unhandled TypeError by sending a key such as constructor.

This is the same class of bug as GHSA-4h5r-cv8j-4456 in @orpc/json-schema, in a different package.

This is not global prototype pollution

The global Object.prototype is never modified. No shared or global state is written, so other objects in the process, other requests, and other users are not affected.

What changes is the prototype of one object: the value the coercer returns for the request that carried the payload. That object is passed on to validation and to the procedure handler, then discarded when the request ends. An ordinary {} created anywhere else in the application is unaffected.

Details

1. __proto__ replaces the prototype of the coerced value

Coerced properties were collected into a plain {}, so newObj['__proto__'] = value triggers the inherited __proto__ setter instead of creating an own property. Verified on 1.14.8 against a record schema, in both plugins:

schema: z.record(z.string())
input:  {"a":"1","__proto__":{"polluted":true}}

Object.keys(result)  => ["a"]      // the __proto__ entry is dropped from the output
result.polluted      => true       // the attacker object is now this object's prototype
({}).polluted        => undefined  // Object.prototype is untouched

The returned value inherits attacker controlled properties, and any consumer reading them through the prototype chain, such as a handler checking input.isAdmin, an Object.assign copy, or a config lookup, sees data the client never legitimately supplied. How far that goes depends on what the application does with the coerced input. There is no generic escalation path.

This affects the record branch of both plugins, and the object branch of both plugins for any key that does not first trigger the error below.

2. Sub-schemas resolved through the prototype chain

The object branch looked up sub-schemas with shape[key], which returns inherited members for keys such as constructor, toString and __proto__. Those values, for example the Object constructor, were then dereferenced as Zod schemas:

schema: z.object({ a: z.number() })
input:  { a: '123', constructor: '456' }

zod 3 => TypeError: Cannot read properties of undefined (reading 'Symbol(ORPC_CUSTOM_ZOD_DEF)')
zod 4 => TypeError: Cannot read properties of undefined (reading 'def')

Coercion throws before the assignment, so for object schemas these keys break the request rather than injecting a prototype. Any client can trigger this on its own requests. It does not affect other clients or the process as a whole.

Impact

Any oRPC server that installs ZodSmartCoercionPlugin or experimental_ZodSmartCoercionPlugin from @orpc/zod and has at least one procedure whose input schema contains an object or a record. No authentication or user interaction is required beyond what the procedure itself demands.

The effect is scoped to the request that sent the payload. Because no global state is written, one request cannot influence another, and restarting the process is not needed to recover.

Patch

Fixed in 1.14.10.

In packages/zod/src/coercer.ts and packages/zod/src/zod4/coercer.ts:

  • collect coerced object and record properties into NullProtoObj, the null prototype helper already used for untrusted keys in bracket-notation.ts and rpc-matcher.ts, so __proto__ stays an ordinary own property and is preserved in the output
  • guard the shape lookup with Object.hasOwn, so unknown keys fall through to catchall as they already do for every other name

PR: middleapi/orpc#1727

Workarounds

Reject or strip request payloads containing __proto__, constructor or prototype keys before they reach the handler, or remove the plugin if automatic coercion is not required.

Note that SmartCoercionPlugin from @orpc/json-schema, the recommended replacement for these plugins, was affected by the same class of issue (GHSA-4h5r-cv8j-4456) and is fixed in the same release.

Credit

Reported and fixed internally.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npm@orpc/zodall versions1.14.10npm install @orpc/zod@1.14.10

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @orpc/zod, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update @orpc/zod to 1.14.10 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-gcgf-fh7c-8gf2 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

Frequently Asked Questions

## Summary `ZodSmartCoercionPlugin` and `experimental_ZodSmartCoercionPlugin` mishandle object keys that name `Object.prototype` members. Both coerce request input before validation, so any client that can reach a procedure whose input schema contains an object or a record can: 1. replace the prototype of the coerced input object, and 2. make a request fail with an unhandled `TypeError` by sending a key such as `constructor`. This is the same class of bug as GHSA-4h5r-cv8j-4456 in `@orpc/json-schema`, in a different package. ## This is not global prototype pollution **The global `Object.p
O3 Security · Impact-Aware SCA

Is GHSA-gcgf-fh7c-8gf2 in your dependencies?

Find it across npm, including transitive dependencies.

@orpc/zod: Prototype injection in smart coercion