FlawOpen

flawopen.com/Prototype pollution/JSON.parse()

Does JSON.parse() cause prototype pollution?

CWE-1321: Prototype PollutionReference guide
Short answer

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.

UNSAFE COPY — LOCAL TARGET ONLY
// 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
SAFE HANDLING — KNOWN SHAPE
// 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

Should I always use a reviver to strip __proto__?

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.

Does Express or body-parser protect me?

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.

Is JSON.stringify() a prototype-pollution risk?

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.

References

See also JavaScript Prototype Pollution: Causes & Fixes Is JSON.parse() safer than eval()? Is lodash.merge() safe from prototype pollution? Object.assign() and Prototype Pollution: What Is Safe?