← All fixes

Fix it

Prototype pollution from unsafe object merge — how to fix it

High severityCWE-1321 (Improperly Controlled Modification of Object Prototype Attributes)
We scanned nearly 2,000 AI-built apps and 1 in 8 shipped a high-severity flaw. Is this one in yours?Scan free →

Prototype pollution happens when code copies keys from untrusted input into an object without filtering, and one of those keys is __proto__, constructor, or prototype. Writing through them modifies Object.prototype, so the change leaks into every object in the app. Depending on what your code later reads off objects, this ranges from broken logic to authorization bypass to remote code execution. The fix is to reject those keys and merge with safe primitives.

Why it's a problem

Because almost every object inherits from Object.prototype, polluting it is a global side effect from a single request. An attacker can inject a property that your code later trusts, for example an isAdmin flag or an option that flips a dangerous code path on. The input looks like ordinary JSON, and the effect shows up far from the merge, which makes it hard to trace.

The pattern

// naive recursive merge copies whatever keys the input contains
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];   // key could be "__proto__"
    }
  }
  return target;
}
merge({}, JSON.parse(req.body));

The fix

const BLOCKED = new Set(["__proto__", "constructor", "prototype"]);

function safeMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (BLOCKED.has(key)) continue;              // never copy dangerous keys
    const val = source[key];
    if (val && typeof val === "object" && !Array.isArray(val)) {
      target[key] = safeMerge(target[key] ?? Object.create(null), val);
    } else {
      target[key] = val;
    }
  }
  return target;
}
safeMerge(Object.create(null), JSON.parse(req.body));

Why AI tools write this

A recursive merge is a common utility, and the straightforward version using for-in copies every enumerable key without a second thought. The assistant writes the version that passes an obvious test, and the __proto__ case never appears in normal data, so the missing guard is invisible until someone sends it deliberately.

The quick fix

  • Reject or skip __proto__, constructor, and prototype keys in any merge or assign over untrusted input.
  • Iterate with Object.keys and build onto Object.create(null) rather than a plain {}.
  • Prefer a maintained library (lodash merge is hardened) over a hand-rolled deep merge.
  • Validate input against a schema so unexpected keys are dropped before merging.

Want to know if this pattern is already in a repo you shipped? Scan a public repo free, no account needed.

Scan a repo →

Related: Mass assignment

Prototype Pollution via Unsafe Merge — Why It's Dangerous and the Fix: Prbl