Prototype Pollution
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches' (d5), semgrep/snyk rules flag recursive merges and unsafe key assignment, but plain code review often misses it since the vulnerable pattern (_.merge) looks innocuous.
Closest to 'simple parameterised fix' (e3), quick_fix says swap recursive merge for safe alternatives, add __proto__/constructor key validation, or use Object.create(null) — a targeted pattern replacement rather than architectural change.
Closest to 'localised tax' (b3), applies to object-handling code paths that accept user input; doesn't shape the whole system but recurs wherever deep merge / dynamic key assignment is used.
Closest to 'serious trap' (t7), misconception explicitly notes devs assume it's browser-only and that __proto__ assignment is harmless, but it silently mutates global Object.prototype affecting every object in the process — contradicts intuition about object scoping.
Also Known As
TL;DR
Explanation
Prototype pollution is a JavaScript-specific vulnerability where user-controlled keys like __proto__ or constructor.prototype are used to write properties onto Object.prototype. Because all JavaScript objects inherit from Object.prototype, injected properties propagate globally and can override application logic, bypass security checks, or escalate to remote code execution in server-side Node.js contexts. PHP is not affected, but PHP applications serving JavaScript-heavy frontends should sanitise JSON inputs and use libraries with known mitigations.
How It's Exploited
{"__proto__": {"isAdmin": true}}
# All subsequent {} objects have isAdmin: true → auth bypass
Common Misconception
Why It Matters
Common Mistakes
- Deep-merging objects using recursive assignment without checking for __proto__ or constructor keys.
- Using user-supplied strings as keys in object assignment — an attacker sends __proto__[isAdmin]=true.
- Not using Object.create(null) for dictionaries/maps that accept user-controlled keys.
- Ignoring prototype pollution in server-side Node.js — it's not only a browser problem.
Code Examples
// JavaScript — merging user object pollutes Object.prototype
function merge(target, source) {
for (let key in source) {
target[key] = source[key]; // key could be '__proto__'
}
}
merge({}, JSON.parse('{"__proto__":{"admin":true}}'));
console.log({}.admin); // true — every new object is now 'admin'
// Use Object.keys() — own keys only, no prototype chain
function safeMerge(target, source) {
for (let key of Object.keys(source)) {
if (['__proto__','constructor','prototype'].includes(key)) continue;
target[key] = source[key];
}
}
// Or use Object.create(null) — no prototype at all
const safe = Object.create(null);
// Or JSON round-trip for deep clone of untrusted data:
const clone = JSON.parse(JSON.stringify(untrusted));
// PHP note: not applicable — PHP objects don't share a mutable prototype