flawopen.com/Prototype pollution/JSON.parse()
Does JSON.parse() cause prototype pollution?
No. JSON.parse() creates an own data property named __proto__; parsing does not invoke the inherited setter or change a prototype. Risk begins only when later code copies that key through a setter, recursively merges untrusted keys, or writes through an attacker-controlled object path.
// JSON.parse creates data; it does not pollute a prototype. const input = '{"__proto__":{"isAdmin":true}}'; const parsed = JSON.parse(input); // Object.assign uses Set on the destination. This changes // only this local object's prototype in this demonstration. const copied = Object.assign({}, parsed); Object.getPrototypeOf(copied).isAdmin; // true Object.prototype.isAdmin; // undefined
// Parse, then construct only the fields the application accepts. const input = '{"theme":"dark","locale":"en","__proto__":{"isAdmin":true}}'; const parsed = JSON.parse(input); const config = { theme: parsed.theme, locale: parsed.locale, }; // For map-like untrusted keys, avoid inherited setters. const options = Object.assign(Object.create(null), parsed);
Why JSON.parse() is not the pollution sink
A JSON key and an object-literal prototype setter are different things. JSON parsing defines own data properties on a new value; it does not execute obj.__proto__ = value. Later operations can behave differently: Object.assign(target, source) uses assignment on the target, so an own __proto__ key can invoke the target’s inherited setter. That changes the target object’s prototype; by itself, it does not modify Object.prototype.
After const o = JSON.parse('{"__proto__":{"a":1}}'), Object.hasOwn(o, "__proto__") is true while Object.prototype.a remains undefined. If a later copy assigns this key to a normal target, that target can receive a different prototype. Object spread creates own data properties instead of invoking the setter, but it is not a defense for unsafe recursive merge or path-setting code.
Where the real risk appears
Parsed JSON is untrusted data, not a polluted prototype. The danger depends on downstream sinks: a recursive merge, a path setter, or a copy loop that performs assignments on an ordinary object. A vulnerable recursive merge may follow __proto__ or constructor.prototype into a shared prototype; a one-level copy may instead alter only its target. Trace the exact helper and target before describing impact.
Review where untrusted keys are written, not just where JSON is parsed: deep-merge helpers, object-path setters, and for...in assignment loops. Reject __proto__, constructor, and prototype where keys are attacker-controlled; prefer a schema-built known shape or a null-prototype dictionary.
FAQ
A reviver can drop that key at parse time, which is useful when the application does not need it. It is defense in depth, not a replacement for fixing unsafe merge and path-setting sinks; constructor.prototype and non-JSON sources may still matter.
Body parsing produces a JavaScript value; it does not make later copies or merges safe. Review the exact parser and downstream operations. Some query-string parsers also construct nested objects from bracket notation, which is a separate input path.
No. Serialization reads values and produces JSON text; it does not change an object’s prototype. The risk may return when that text is parsed and then passed to an unsafe sink.