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

Does JSON.parse() cause prototype pollution?

CWE-1321: Prototype PollutionReference page
Short answer

No. JSON.parse() creates an ordinary own property literally named __proto__ and never invokes the inherited setter, so parsing alone is safe. Pollution happens in the next step — when that parsed object is merged, cloned, or copied key-by-key onto another object.

THE DANGEROUS SECOND STEP
const evil = JSON.parse(
  '{"__proto__":{"isAdmin":true}}'
);

// Parsing was safe. This is not:
deepMerge(config, evil);

// Because the merge does:
//   target['__proto__']  ← READ returns
//   the real Object.prototype, then
//   writes isAdmin into it.

({}).isAdmin;  // → true
SAFE HANDLING
// 1. Strip the key during parsing
const data = JSON.parse(input, (k, v) =>
  k === '__proto__' ? undefined : v
);

// 2. Or read explicit fields only —
//    never copy the whole object
const cfg = {
  theme:  data.theme,
  locale: data.locale,
};

// 3. Or validate with a schema library
//    that returns a new, known-shape
//    object rather than the input.

Why parsing itself is safe

There are two different operations that look identical in source code. A plain assignment (obj.x = v) performs a set, which walks the prototype chain looking for a setter — and __proto__ has one. JSON.parse() instead defines own properties directly on the new object, bypassing the accessor.

You can confirm this: after const o = JSON.parse('{"__proto__":{"a":1}}'), the object o has an own key __proto__ holding a plain object, and Object.prototype.a remains undefined. The prototype chain is untouched.

Why this still matters in review

The practical risk is that a parsed object carries a live payload waiting for careless downstream handling. Request bodies parsed by a web framework, configuration files, cached JSON documents and message-queue payloads all become polluted-object carriers the moment any generic copy routine touches them.

The useful review question is therefore not "where do we parse JSON?" but "where do we copy untrusted keys onto an existing object?" — deep merges, object-path setters, and for...in loops that assign.

FAQ

Should I always use the reviver to strip __proto__?

It is cheap and removes the payload at the boundary, which is a reasonable default for untrusted input. It is not a substitute for fixing an unguarded deep merge, since pollution can arrive from sources other than JSON.

Does Express or body-parser protect me?

Body parsing produces the object; it does not decide what you do with it. Some query-string parsers additionally build nested objects from bracket syntax, which is its own source of attacker-controlled nested keys. Check what your framework's parser does with a[__proto__][b]=c.

Is JSON.stringify() a risk?

No. Serialising reads properties and produces a string; it cannot modify a prototype.

References