flawopen.com/Prototype Pollution/Explained
A JavaScript-only bug class where attacker-controlled input writes to the key __proto__, modifying Object.prototype — the object every other object inherits from. One write silently adds a property to every object in the running program, including ones created later.
// A naive recursive merge function merge(target, source) { for (const key in source) { if (typeof source[key] === 'object') { target[key] = target[key] || {}; merge(target[key], source[key]); } else { target[key] = source[key]; } } } // Attacker sends this JSON: // {"__proto__": {"isAdmin": true}} merge({}, JSON.parse(userInput)); ({}).isAdmin; // → true. Everywhere.
const BLOCKED = new Set([ '__proto__', 'constructor', 'prototype' ]); function merge(target, source) { for (const key of Object.keys(source)) { if (BLOCKED.has(key)) continue; // ... recurse safely } } // Or avoid inheritance entirely: const safe = Object.create(null); // Or freeze the prototype at startup: Object.freeze(Object.prototype);
JavaScript uses prototypal inheritance. Every object holds a link to a prototype object, and property lookups that miss on the object itself walk up that chain. Almost every plain object ultimately inherits from a single shared Object.prototype.
Crucially, __proto__ is an accessor that exposes that link as an ordinary-looking string key. So code that assigns properties from untrusted data — a merge, a clone, a query-string parser, a config loader — can be tricked into writing through it. There is no equivalent in Python, Java, Go or Rust, where a class is not a mutable object reachable by writing a string key on an instance.
Polluting the prototype rarely causes damage by itself. It becomes exploitable because other code later reads a property it expects to be absent:
if (user.isAdmin) now passes for every user object.shell or NODE_OPTIONS, or a function-constructing library.toString breaks unrelated code across the whole process.Because the effect is global and persists for the process lifetime, the source of the pollution and the place it causes harm are often in completely different modules — which makes these bugs hard to trace.
Any code that copies untrusted keys onto an object: deep-merge and deep-clone utilities, query-string and form parsers that build nested objects from a[b][c]=d syntax, configuration loaders, and object-path setters such as set(obj, userPath, value).
No. JSON.parse() creates a normal own property named __proto__ and does not follow the setter. The danger appears when the resulting object is then merged or copied onto another object by code that does trigger it. See does JSON.parse() cause prototype pollution?
It blocks the most common sink and is a reasonable defence-in-depth measure, but it can break libraries that legitimately extend built-ins, and it does not protect other prototypes such as Array.prototype. Treat it as a backstop, not the fix.
No. Types are erased at runtime, and the pollution happens at runtime. A value typed as Record<string, string> can still carry a __proto__ key.