Prevent prototype pollution by keeping untrusted keys out of ordinary objects, so user input can never reach the prototype every object shares. Use Map or Set for key and value collections, create lookup objects with Object.create(null), and consider freezing built in prototypes. In Node.js, --disable-proto=delete adds a further layer.
What prototype pollution is
In JavaScript, objects inherit from a shared prototype. If an attacker can make your code write to a key such as __proto__ or constructor.prototype, they change properties that many other objects inherit. OWASP describes the result as unauthorized data access, privilege escalation and, in some cases, remote code execution.
The usual cause is a function that merges, clones or sets nested properties using keys taken from user input.
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === "object") {
target[key] = merge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
If source came from JSON.parse of attacker data containing a __proto__ key, this kind of loop can end up writing onto the shared prototype.
Defense 1: use Map and Set
For collections of keys and values, such as allowed tags or options, OWASP recommends new Set() or new Map() instead of object literals. A Map key is just a key. It has no special meaning for inheritance.
Defense 2: objects with no prototype
If you need a plain object, create it with Object.create(null) so it does not inherit from Object.prototype. As a last resort, OWASP mentions {__proto__: null}. A prototype free object cannot be used to reach a shared prototype through itself.
Defense 3: freeze built in prototypes
Object.freeze(Object.prototype) stops code from adding or changing properties on it. Two cautions from OWASP:
- Freezing is shallow, so objects referenced from the frozen object stay changeable unless you freeze them too.
- Some libraries modify built in prototypes and may break, so test before rolling it out.
Object.seal blocks adding and removing properties but still lets existing values change, so OWASP says not to rely on it for this purpose.
Defense 4: Node.js flag
Node can run with --disable-proto=delete, which removes the __proto__ property entirely. OWASP calls this defense in depth and notes that pollution through constructor.prototype is still possible, so it complements the other steps instead of replacing them.
Defense 5: review the merge code you use
Most real cases involve a deep merge, clone or "set by path" helper, either your own or a library. Check:
- Does it skip
__proto__,constructorandprototypekeys? - Is the library kept current?
- Does it run on request bodies, query strings or parsed config?
A dependency scan tells you which packages in your tree have had prototype pollution fixes. Safeguard's dependency scanning and reachability analysis can show whether your code reaches the affected function. Try the free scan at gold.safeguard.sh/scan.
Quick check
Does JSON.parse cause prototype pollution?
Not by itself. It can produce an own property named __proto__, and a later unsafe merge or assignment can then pollute the shared prototype.
Is Object.freeze on the prototype enough?
It helps, but it is shallow and may break libraries. Combine it with safer data structures and careful merge code.
Sources
Last reviewed: 19 October 2026.