Prototype Pollution in JavaScript: Client and Server Side
Prototype pollution is a JavaScript-specific vulnerability. By injecting properties into Object.prototype, an attacker can change the behaviour of every object in the application, sometimes with dramatic results.
A quick refresher on prototypes
In JavaScript, objects inherit properties from their prototype. Nearly every object ultimately inherits from Object.prototype. If you add a property there, every object appears to have it:
Object.prototype.isAdmin = true;
const user = {};
console.log(user.isAdmin); // true
How pollution happens
The usual cause is a function that merges, clones or sets nested properties from user-controlled input without blocking special keys:
// Vulnerable deep merge
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === "object") merge(target[key] ??= {}, source[key]);
else target[key] = source[key];
}
}
merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
Keys like __proto__ and constructor.prototype let the input reach Object.prototype.
Other sources include query string parsers that support nested objects, such as ?__proto__[isAdmin]=true, and path-based setters like set(obj, "a.b.c", value).
Impact on the server side
In Node.js, polluted properties can affect application logic and libraries:
- Privilege escalation: checks like
if (user.isAdmin)become true for everyone. - Denial of service: polluting properties that break core functions.
- Remote code execution: through "gadgets" in libraries, for example polluting options used when spawning child processes or rendering templates.
Impact on the client side
In the browser, pollution can combine with script gadgets in libraries to produce DOM XSS, for example by polluting a property a library later uses as HTML or a script URL.
How to prevent prototype pollution
- Block dangerous keys in merge, clone and set functions:
__proto__,constructorandprototype. - Use safe data structures:
Mapfor user-controlled keys, or objects created withObject.create(null), which have no prototype. - Validate input with a schema, rejecting unexpected keys.
- Keep libraries updated; many popular utilities have patched pollution bugs.
- Freeze the prototype in hardened environments with
Object.freeze(Object.prototype), testing for compatibility. - Use Node.js
--disable-protooptions where suitable, to remove the__proto__accessor.
const BLOCKED = new Set(["__proto__", "constructor", "prototype"]);
function safeSet(obj, key, value) {
if (BLOCKED.has(key)) return;
obj[key] = value;
}
Testing for prototype pollution
Testing sends payloads using __proto__ and constructor.prototype in JSON bodies and query strings, then looks for changed behaviour. SecurityInfinity agents test for pollution sources and flag vulnerable dependencies through static analysis. Start free.
Frequently asked questions
Is prototype pollution only a Node.js problem?
It affects JavaScript in both browsers and Node.js. Other languages have related issues but not this exact mechanism.
Does using TypeScript prevent it?
No. TypeScript types are removed at runtime and do not stop malicious keys in parsed input.
How serious is prototype pollution?
It ranges from low to critical depending on available gadgets. Server-side pollution leading to RCE is critical.