Safeguard
Security Guides•

How to prevent prototype pollution in JavaScript and Node.js

Prototype pollution lets attackers change shared object behavior. Practical defenses: Map, Object.create(null), freezing prototypes and --disable-proto.

Safeguard Security Team
3 min read

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__, constructor and prototype keys?
  • 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.