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
Sub-issue of #488 (but independent and security-relevant; can land first).
packages/core/src/serialize.jsdecode()rebuilds a plain object withconst out = {}and assignsout[realKey] = decode(...), whererealKeycan be__proto__/constructor/prototypefrom the UNTRUSTED wire.out['__proto__'] = objsets 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 withcreateNullProtoObject+safeObjectMerge.Design / approach
Decode untrusted wire objects into
Object.create(null), or assign viaObject.definePropertywith a plain data descriptor, so a__proto__/constructorkey 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
__proto__/constructor/prototypekey in the wire does not mutate the decoded object's prototype (counterfactual test fails when the guard is removed).