Prototype pollution is a JavaScript flaw where an attacker manipulates a base object's prototype (usually Object.prototype) via special property names - __proto__, constructor, prototype. Because nearly every object inherits from Object.prototype, poisoning it affects every object in the app. Impact: DoS (break logic), property injection / security-check bypass (force a flag true everywhere), privilege escalation, and - chained to a dangerous sink - DOM XSS (client) or RCE (server, Node.js). Root cause: unsafe recursive merging of untrusted data into objects. Fix: reject/strip dangerous keys, use null-prototype data holders (Object.create(null)), freeze Object.prototype, use patched deep-merge libraries, validate against a schema. Detail below; part of every web app test.
// 01 The one shared parent
To understand prototype pollution you need one fact about JavaScript: almost every object inherits from a single shared parent, Object.prototype. When you access a property on an object and it's not there, JavaScript looks up the prototype chain - ultimately to Object.prototype. That inheritance is normally invisible and useful. The vulnerability: if an attacker can add or change a property on Object.prototype itself, then every object in the application appears to have that property. They do it using special property names - __proto__, constructor, prototype - in input the app processes. The classic trigger is unsafe recursive merging: code that deep-merges user-controlled JSON into an object (a config, a set of options) and, following the attacker's __proto__ key, writes onto the prototype instead of the object. One crafted request, and the pollution touches everything.
// 02 What it yields
Denial of service
Pollute a property that breaks core logic - every object now misbehaves, crashing or hanging the app.
Property injection
Change behaviour or bypass security checks - e.g. make an isAdmin flag appear true on every object.
Privilege escalation
Inherited attacker-controlled properties override intended access decisions.
XSS / RCE (chained)
When the polluted property flows into a sink: DOM XSS client-side, or RCE server-side in Node.js.
It looks subtle, but it's a genuine, sometimes critical vulnerability - not a curiosity. The severity depends on where the polluted property lands: a broken flag is a DoS; a flag that feeds an access-control decision is a bypass; one that reaches a template or process spawn is code execution.
// 03 Client-side vs server-side
Prototype pollution occurs on both sides, with the same mechanism but different worst cases. Client-side pollution happens in browser JavaScript; its headline impact is DOM-based XSS - when a polluted property reaches a dangerous DOM sink (an innerHTML, a script source, a gadget in a library), the attacker's script runs in the victim's page. Server-side pollution happens in Node.js and tends to be more severe: depending on where the polluted prototype flows, it can cause denial of service, security-control bypass, or remote code execution - for instance when a polluted property influences a template engine, a child-process spawn, or a configuration object. Because the underlying cause is identical - unsafe handling of attacker-controlled property names - both should be tested wherever JavaScript merges or copies untrusted input into objects: request bodies, query strings, JSON config, and library options.
// 04 How to test for it
Testing is targeted and gadget-aware. The tester identifies places the app merges or copies untrusted input into objects - JSON request bodies, query/parameter parsing, config merging - and submits payloads containing __proto__, constructor.prototype and similar, setting a distinctive property. Then they check whether that property has leaked onto other objects: a simple probe is to pollute a harmless property and observe whether a fresh, unrelated object now exhibits it. Confirming impact means hunting gadgets - places where a polluted property changes behaviour, reaches a DOM sink (client) or a dangerous server sink (Node.js) - to escalate from “pollution exists” to “XSS/RCE/bypass.” This is manual, JavaScript-aware work: a scanner may flag a reflected __proto__ but rarely traces the pollution to a real gadget, which is the difference between a note and a proven finding.
// 05 How to prevent it
The defence is to ensure attacker-controlled property names can never reach the prototype chain. Concretely: reject or strip dangerous keys (__proto__, constructor, prototype) during parsing and merging; use null-prototype objects - created via Object.create(null) - as data holders, so there's no prototype to pollute; freeze Object.prototype where feasible so it can't be modified; use well-maintained, patched libraries for deep-merge and object manipulation, since many prototype-pollution issues were library flaws now fixed; and validate input against a schema to reject unexpected structures. As with the other injection classes, the durable fix is structural - remove the ability for user input to write to the prototype - rather than trying to blocklist every variant. Every web app and source-review engagement we run checks the merge/copy paths for this, per our methodology.
// 06 Frequently asked questions
What is prototype pollution?
A JavaScript vulnerability where an attacker manipulates a base object's prototype (usually Object.prototype) by supplying input with special property names like __proto__, constructor or prototype. Because almost every object inherits from Object.prototype, changing a property there affects nearly every object - altering behaviour, overriding security checks, causing DoS, or leading to code execution. It typically arises from unsafe recursive merging of user-controlled data.
What can an attacker do with it?
From moderate to critical: denial of service by polluting a property that breaks logic; property injection changing behaviour or bypassing security checks (e.g. forcing an access flag true on every object); privilege escalation; and, when the polluted property reaches a dangerous sink, chaining to DOM XSS (client) or RCE (server, Node.js). It can look subtle but is a genuine, sometimes severe vulnerability.
Client-side or server-side?
Both. Client-side pollution in the browser can lead to DOM-based XSS when the polluted property reaches a dangerous sink. Server-side pollution in Node.js can be more severe - DoS, security-control bypass or RCE when the polluted prototype influences server logic or a process spawn. Same mechanism (unsafe handling of attacker-controlled property names), so test both wherever JavaScript merges untrusted input into objects.
How do you prevent it?
Handle untrusted data safely when merging/copying into objects: reject or strip dangerous keys (__proto__, constructor, prototype); use null-prototype objects (Object.create(null)) as data holders; freeze Object.prototype where feasible; use patched deep-merge libraries; and validate input against a schema. The goal is to ensure attacker-controlled property names can never reach the prototype chain.
// 07 Related reading
- Mass assignment — a related object-tampering class.
- SSTI and command injection — sinks pollution can reach.
- Web app testing and source code review.