Skip to content

fix: harden the wire deserializer against prototype pollution #491

@vivek7405

Description

@vivek7405

Sub-issue of #488 (but independent and security-relevant; can land first). packages/core/src/serialize.js decode() rebuilds a plain object with const out = {} and assigns out[realKey] = decode(...), where realKey can be __proto__ / constructor / prototype from the UNTRUSTED wire. out['__proto__'] = obj sets the decoded object's prototype rather than an own property, so a deserialized action argument can arrive with a polluted prototype surfacing injected fields. It is object-local (not global pollution), but still a vector for an action that trusts its arg shape. TanStack neutralizes this with createNullProtoObject + safeObjectMerge.

Design / approach

Decode untrusted wire objects into Object.create(null), or assign via Object.defineProperty with a plain data descriptor, so a __proto__ / constructor key becomes an ordinary own property instead of mutating the prototype. Verify the Map/Set/Error decoders and the ref table are also safe. Add a counterfactual test (a {"__proto__": {"isAdmin": true}} payload must NOT pollute the decoded object's prototype chain). Keep round-trip fidelity for legitimately-keyed __proto__ data.

Acceptance criteria

  • A malicious __proto__ / constructor / prototype key in the wire does not mutate the decoded object's prototype (counterfactual test fails when the guard is removed).
  • Round-trip fidelity preserved for objects that legitimately carry such keys as data.
  • No measurable perf regression on the hot decode path.
  • Tests (unit + a serializer security test) + a short note in the serializer docs.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions