flawopen.com/Prototype Pollution/Object.assign()

Is Object.assign() safe from prototype pollution?

CWE-1321: Prototype PollutionReference page
Short answer

Yes — on its own. Object.assign() copies only own enumerable properties and uses a definition that does not invoke the inherited __proto__ setter, so a single shallow assign cannot pollute the prototype. The danger is the deep-merge function someone builds on top of it.

UNSAFE — the deep merge around it
// Recursion is what reintroduces the bug
function deepAssign(target, source) {
  for (const k of Object.keys(source)) {
    if (source[k] && typeof source[k] === 'object') {
      target[k] = target[k] || {};
      deepAssign(target[k], source[k]);
      // k === '__proto__' here means
      // target['__proto__'] is READ,
      // returning Object.prototype,
      // which is then written into.
    } else {
      Object.assign(target, { [k]: source[k] });
    }
  }
}
SAFE
// Shallow assign — no pollution possible
const merged = Object.assign({}, userInput);

// Spread is equivalent and also safe
const merged2 = { ...userInput };

// For deep merges, skip dangerous keys
// and use a null-prototype accumulator
const out = Object.create(null);
for (const k of Object.keys(src)) {
  if (k === '__proto__') continue;
  if (k === 'constructor') continue;
  if (k === 'prototype') continue;
  out[k] = src[k];
}

Why the shallow case is safe

Assignment through = performs a set, which walks the prototype chain looking for a setter — and finds the accessor that __proto__ installs on Object.prototype. Object.assign() instead performs a define-like operation on own properties, which creates a plain data property named "__proto__" on the target rather than invoking the accessor.

The practical consequence is that Object.assign({}, JSON.parse(evil)) gives you an object with a harmless own key literally called __proto__. Nothing global changes.

Where it goes wrong anyway

Almost every real prototype pollution bug in the wild is in recursive merge code. The recursive step usually reads target[key] to find or create a nested object. When key is "__proto__", that read returns the actual Object.prototype — and the next level of recursion writes straight into it.

So the question to ask in review is not "does this use Object.assign?" but "does anything here index into the target with an attacker-controlled key?"

FAQ

Is object spread ({ ...obj }) equally safe?

Yes. Spread uses the same own-property copy semantics as Object.assign() and does not trigger the setter. Both are safe shallow operations.

What about structuredClone()?

It is safe for this purpose — it does not preserve or apply prototypes from the source, producing plain objects.

Should I just use a library?

Use a maintained one and keep it current. Several widely used merge and object-path utilities have had prototype pollution advisories; the bug class is well known to their maintainers now, but only patched versions carry the fix.

References