Skip to content

fix(serializer): prevent unsafe deserialization paths#1543

Merged
Tomas2D merged 1 commit into
i-am-bee:mainfrom
YinkaMetrics:yinka/fix-ts-serializer-prototype-pollution
Jul 3, 2026
Merged

fix(serializer): prevent unsafe deserialization paths#1543
Tomas2D merged 1 commit into
i-am-bee:mainfrom
YinkaMetrics:yinka/fix-ts-serializer-prototype-pollution

Conversation

@YinkaMetrics

Copy link
Copy Markdown
Contributor

Summary

  • reject unsafe object path keys in setProp before nested assignment
  • add serializer regression coverage for __proto__, constructor, and prototype paths
  • close the public TypeScript serializer prototype-pollution tracker

Fixes #1532.

Verification

  • yarn vitest src/serializer/serializer.test.ts --run
  • yarn vitest run src
  • yarn tsc --noEmit
  • yarn eslint src/internals/helpers/object.ts src/serializer/serializer.test.ts
  • yarn prettier --log-level warn --check .
  • NODE_OPTIONS=--max-old-space-size=16384 yarn tsup

@YinkaMetrics
YinkaMetrics requested a review from a team as a code owner July 1, 2026 00:14
@dosubot dosubot Bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Jul 1, 2026
@github-actions github-actions Bot added the typescript Typescript related functionality label Jul 1, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a security check in setProp to prevent prototype pollution by blocking unsafe path keys (proto, constructor, and prototype), along with corresponding unit tests. The reviewer points out that blocking constructor and prototype is overly restrictive and unnecessary, as the existing type guards already prevent traversing constructor functions. It is recommended to only block proto and update the tests accordingly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

return (target: T | undefined) => keys.every((key) => hasProp(target, key));
}

const UnsafePathKeys = new Set<keyof any>(["__proto__", "constructor", "prototype"]);

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.

medium

While blocking "__proto__" is absolutely necessary to prevent prototype pollution, blocking "constructor" and "prototype" is overly restrictive and can break legitimate use cases (e.g., setting or traversing properties named "constructor" or "prototype" on plain objects, such as metadata or configuration objects).

Because setProp strictly enforces that the target at each step must be a plain object or an array (!R.isPlainObject(target) && !R.isArray(target)), any attempt to traverse through a real constructor function (like Object or Array) will automatically throw a TypeError on the next iteration.

For example, if paths is ["constructor", "prototype", "polluted"]:

  1. In the first iteration, target becomes the global Object constructor function.
  2. In the second iteration, the check !R.isPlainObject(target) && !R.isArray(target) will fail because Object is a function, throwing "Only plain objects and arrays are supported!".

Thus, prototype pollution via constructor.prototype is already inherently prevented by the existing type guards. We only need to block "__proto__" to be fully secure while maintaining compatibility with legitimate use cases.

Suggested change
const UnsafePathKeys = new Set<keyof any>(["__proto__", "constructor", "prototype"]);
const UnsafePathKeys = new Set<keyof any>(["__proto__"]);

Comment on lines +206 to +208
it.each(["__proto__", "constructor", "prototype"])(
'Rejects deserialization path key "%s"',
async (unsafeKey) => {

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.

medium

Update the test to only verify the rejection of the "__proto__" path key, as "constructor" and "prototype" are safe to traverse on plain objects and are already protected from prototype pollution by the isPlainObject check.

  it(
    'Rejects deserialization path key "__proto__"',
    async () => {
      const unsafeKey = "__proto__";

Signed-off-by: Yinka Metrics <115735904+YinkaMetrics@users.noreply.github.com>
@YinkaMetrics
YinkaMetrics force-pushed the yinka/fix-ts-serializer-prototype-pollution branch from 1fdd0f0 to 28f2b94 Compare July 1, 2026 00:33
@YinkaMetrics

Copy link
Copy Markdown
Contributor Author

Updated the patch to address the compatibility concern from the automated review.

The guard now rejects only __proto__, and setProp uses own-property lookup when reusing intermediate objects. That keeps constructor and prototype usable as normal data keys while avoiding inherited prototype lookups during deserialization. I also added regression coverage for both cases.

@Tomas2D Tomas2D 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.

Correct, well-scoped fix — blocks the __proto__ path key and hardens the nested assignment, with solid regression tests confirming constructor/prototype stay safe as data keys. CI green. Thanks @YinkaMetrics! 🐝

@Tomas2D
Tomas2D merged commit 04d1c2d into i-am-bee:main Jul 3, 2026
3 checks passed
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:XS This PR changes 0-9 lines, ignoring generated files. typescript Typescript related functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🚨 HIGH Severity: Prototype Pollution via setProp() in TypeScript Serializer - GHSA-cq4p-f9fc-wpqc

2 participants