Skip to content

feat(server): add PrototypePollutionProtectionHandlerPlugin - #1961

Merged
dinwwwh merged 4 commits into
mainfrom
claude/handler-plugin-malicious-input-cf327f
Aug 27, 2026
Merged

feat(server): add PrototypePollutionProtectionHandlerPlugin#1961
dinwwwh merged 4 commits into
mainfrom
claude/handler-plugin-malicious-input-cf327f

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Aug 27, 2026

Copy link
Copy Markdown
Member

Adds PrototypePollutionProtectionHandlerPlugin to @orpc/server/plugins. It rejects any request whose decoded input contains an own __proto__ key, or a constructor key holding a prototype key, with a 400 BAD_REQUEST before the procedure runs — the same rule secure-json-parse enforces for Fastify. oRPC's own decoding is already pollution-safe; the plugin stops these keys from reaching application code that merges, clones, or path-sets input with a vulnerable utility.

Behavior

  • Runs as a client interceptor, so every matched request on any handler is checked: JSON and query data in RPCHandler, form data and bracket notation in OpenAPIHandler.
  • Walks plain and null-prototype objects, arrays, and Map/Set keys and values at any depth, iteratively with a cycle guard, so nesting deeper than the call stack still gets a 400 instead of a RangeError; non-container objects such as File and Date pass untouched.
  • An AsyncIteratorObject input is checked value by value as it arrives; a polluting value fails that iteration with the same error.
  • Lone constructor or prototype keys stay allowed; neither alone can pollute.

Testing

  • 28 new tests cover block/allow cases at the interceptor level, AsyncIteratorObject inputs, and RPCHandler integration with raw JSON bodies; the full server suite (532 tests) passes.
  • pnpm type:check, eslint, and the docs JSDoc backlink checker all pass.

Docs

  • New page /docs/plugins/prototype-pollution-protection documents how the check works and setup.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
orpc 1379f9e Commit Preview URL

Branch Preview URL
Aug 27 2026, 07:46 AM

@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

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

@orpc/arktype

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

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@1961

@orpc/client

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

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@1961

@orpc/contract

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

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@1961

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@1961

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@1961

@orpc/json-schema

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

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@1961

@orpc/nest

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

@orpc/next

npm i https://pkg.pr.new/@orpc/next@1961

@orpc/node

npm i https://pkg.pr.new/@orpc/node@1961

@orpc/openapi

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

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@1961

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@1961

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@1961

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@1961

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@1961

@orpc/server

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

@orpc/shared

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

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@1961

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/zod

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

commit: 1379f9e

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 30 untouched benchmarks


Comparing claude/handler-plugin-malicious-input-cf327f (1379f9e) with main (d63ea85)

Open in CodSpeed

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Minor suggestions only — the plugin is correct, well-tested, and consistent with existing plugin conventions. One robustness edge worth a look.

Reviewed changes

  • PrototypePollutionProtectionHandlerPlugin — new StandardHandlerPlugin that prepends a client interceptor rejecting any matched request whose decoded input carries an own __proto__ key or a constructor-holding-prototype pair with a 400 BAD_REQUEST, mirroring secure-json-parse.
  • Detection walk — recurses plain/null-prototype objects, arrays, and Map/Set keys and values with a cycle guard, leaving non-containers (File, Date) untouched; lone constructor/prototype keys stay allowed.
  • 23 tests — interceptor-level block/allow/cycle cases plus RPCHandler integration against raw JSON bodies; strict assertions, verified to pass.
  • Docs page/docs/plugins/prototype-pollution-protection covering setup and the event-iterator / all-or-nothing / defense-in-depth limitations; naming matches sibling plugin docs.
  • Exports — one line added to plugins/index.ts.

The design is sound and I verified the walking logic independently: isPlainObject does include null-prototype objects (so the __proto__-on-null-proto block test is meaningful), Object.hasOwn-based checks correctly tolerate lone constructor/prototype keys, and the prepend ordering matches SmartCoercionHandlerPlugin. One inline robustness suggestion below.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread packages/server/src/plugins/prototype-pollution-protection.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ The new streaming-input guard is correct, well-tested, and consistent with the shared override/wrapAsyncIterator pattern. One previously-noted robustness edge remains open (see the existing thread on the recursion walk).

Reviewed changes

  • Streaming-input guard — the interceptor now detects an AsyncIteratorObject input and wraps it with override(input, wrapAsyncIterator(input, { mapResult })), so each yielded value and the return value are run through containsPollutingKey as they arrive; a polluting value fails the next() promise with the same 400 BAD_REQUEST.
  • Three new non-vacuous tests — guarded-iterator identity change plus benign for await pass-through; mid-stream rejection (benign yield resolves, polluting yield rejects with the exact ORPCError); and a return-value check via generator return.
  • Docs updated — the walk description now states async-iterator inputs are checked value by value, and the "event iterator payloads pass uninspected" limitation was removed.

The implementation mirrors the established output-side pattern in packages/shared/src/interceptor.ts (same override + wrapAsyncIterator, same @warning comment), and isAsyncIteratorObject is the canonical detection used across the framework. The guard covers both next()-driven and for await consumption, which the tests exercise. No new issues in this delta.

The prior review's inline thread on the unbounded recursion in containsPollutingKey (deep parse-acceptable nesting → RangeError/500 instead of 400) is unaffected by this commit and remains open.

Pullfrog  | Fix it ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Docs-only change reviewed — one informational note on the removed "Limitations" section.

Reviewed changes

  • Removed the "Limitations" section from the plugin docs page — dropping the "defense in depth, not a substitute for safe code" bullet and the "rejection is all-or-nothing with a generic BAD_REQUEST" caveat. No source, test, or export changes in this delta.

ℹ️ The all-or-nothing caveat was worth keeping

The removed "Limitations" section carried two genuinely useful, non-obvious points that are not covered elsewhere on the page: the defense-in-depth framing (plug vulnerabilities from merging/cloning, prefer Object.create(null)/Map) and the operational all-or-nothing behavior — a procedure that legitimately accepts a __proto__/constructor key as data would be silently blocked with a generic 400 and no doc pointer telling the author to drop the plugin for that handler. If the trimming was intentional, that's fine; I'd just want to confirm the all-or-nothing caveat specifically isn't a loss users will trip on.

Pullfrog  | Fix it ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The prior recursion-thread concern is resolved; the iterative walk is correct and well-tested.

Reviewed changes

  • Iterative prototype-pollution walk — replaced the recursive containsPollutingKey with an explicit-stack while loop. Semantically equivalent (same WeakSet cycle guard, array/Map/Set/plain-object handling, and __proto__ / constructor.prototype checks), but bounded heap usage instead of the call stack, so arbitrarily deep input no longer throws a RangeError.
  • Deep-nesting tests — two new cases: a 100k-level benign object is allowed without overflowing the stack, and the same nesting with a polluting __proto__ at the bottom is blocked with the expected 400 BAD_REQUEST.

This directly addresses the prior inline thread (iterative scan was the suggested fix). I re-derived the walk's semantics against the old recursion — behavior is preserved, including the visited cycle guard and short-circuit on first polluting hit — and the full 28-test suite passes. No new issues in this delta.

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

@dinwwwh
dinwwwh merged commit 658f36b into main Aug 27, 2026
11 checks passed
@dinwwwh
dinwwwh deleted the claude/handler-plugin-malicious-input-cf327f branch August 27, 2026 07:51
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