Skip to content

fix(zod): prevent prototype injection in smart coercion#1727

Merged
dinwwwh merged 1 commit into
1.xfrom
claude/zod-coercer-prototype-injection
Jul 25, 2026
Merged

fix(zod): prevent prototype injection in smart coercion#1727
dinwwwh merged 1 commit into
1.xfrom
claude/zod-coercer-prototype-injection

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Jul 25, 2026

Copy link
Copy Markdown
Member

Same two prototype chain bugs as #1726, in ZodSmartCoercionPlugin (zod 3) and experimental_ZodSmartCoercionPlugin (zod 4). Both plugins coerce untrusted input before validation, so the keys below are attacker controlled.

1. Coerced properties were collected into a plain {}

newObj['__proto__'] = value triggers the inherited __proto__ setter instead of creating an own property. This affects the object and record branches in both plugins.

Fix: collect into NullProtoObj, the helper already used for untrusted keys in bracket-notation.ts and rpc-matcher.ts.

2. shape[key] returned inherited members

For keys like constructor, toString and __proto__ the lookup resolves to Object.prototype members, which 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')

The throw happens before the assignment, so in practice these keys made coercion fail rather than injecting a prototype. Any client could break a request this way.

Fix: guard the lookup with Object.hasOwn, so unknown keys fall through to catchall as they already do for every other name.

Verification

zodSmartCoercionPlugin prevents prototype injection added to both test files, covering object, record and catchall. Both fail without the fix. The zod coercer suites pass (583 tests). tsc -b and eslint are clean.

`ZodSmartCoercionPlugin` and `experimental_ZodSmartCoercionPlugin`
collected coerced object and record properties into a plain `{}`, so an
input key named `__proto__` hit the `Object.prototype` setter instead of
creating an own property.

They also looked up sub-schemas with `shape[key]`, which resolves
`constructor`, `toString` and `__proto__` through `Object.prototype`.
Those inherited values were then dereferenced as Zod schemas and threw a
`TypeError`, so any request carrying such a key failed coercion.

Collect into `NullProtoObj` and guard the lookup with `Object.hasOwn`.
@pullfrog

pullfrog Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor
  • Checkout PR fix(zod): prevent prototype injection in smart coercion #1727 and read the full diff
  • Triage: understand the PR — what it changes, domains, contracts
  • Read PR summary snapshot and related code
  • Specialist decision — identify load-bearing hypotheses
  • Dispatch specialists if needed
  • Aggregate findings and draft review
  • Submit review via pullfrog_create_pull_request_review

Pullfrog  | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
orpc Building Building Preview, Comment Jul 25, 2026 3:18pm

@pkg-pr-new

pkg-pr-new Bot commented Jul 25, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@1727

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1727

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1727

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1727

@orpc/experimental-durable-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-iterator@1727

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@1727

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@1727

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1727

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1727

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1727

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@1727

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@1727

@orpc/experimental-pino

npm i https://pkg.pr.new/@orpc/experimental-pino@1727

@orpc/experimental-publisher

npm i https://pkg.pr.new/@orpc/experimental-publisher@1727

@orpc/experimental-publisher-durable-object

npm i https://pkg.pr.new/@orpc/experimental-publisher-durable-object@1727

@orpc/experimental-ratelimit

npm i https://pkg.pr.new/@orpc/experimental-ratelimit@1727

@orpc/react

npm i https://pkg.pr.new/@orpc/react@1727

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@1727

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@1727

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1727

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1727

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@1727

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@1727

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@1727

@orpc/standard-server-fastify

npm i https://pkg.pr.new/@orpc/standard-server-fastify@1727

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@1727

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@1727

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@1727

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@1727

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1727

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1727

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1727

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@1727

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@1727

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1727

commit: 64dbcb7

@dinwwwh
dinwwwh merged commit 26314df into 1.x Jul 25, 2026
4 of 5 checks passed
@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant