Volume 2

Q125: Prototype Pollution and Safe Object Merging

Difficulty: SeniorFrequency: HighAnswer time: 10-14 minutes

What Interviewers Want To Evaluate

This question checks whether you understand one of the most practical JavaScript object security risks. Interviewers want prototype pollution, unsafe deep merge, __proto__, constructor, prototype, object dictionaries, validation, dependency risk, and defense-in-depth.

Senior answers should connect the risk to frontend and Node.js systems, not treat it as trivia.

Short Interview Answer

Prototype pollution happens when attacker-controlled keys modify an object's prototype or a shared prototype such as Object.prototype. This often occurs through unsafe deep merge, path setters, query parsing, or config merging. Dangerous keys include __proto__, constructor, and prototype. Defenses include validating keys, using safe merge libraries, creating dictionaries with Object.create(null), using Map for arbitrary keys, avoiding blind recursive merges, and keeping dependencies patched.

Detailed Interview Answer

JavaScript objects inherit through prototypes. If unsafe code lets external input write prototype-related keys, it can affect many objects.

Example dangerous input:

{
  "__proto__": {
    "isAdmin": true
  }
}

If merged unsafely, later objects may appear to have isAdmin.

Why It Matters

Prototype pollution can cause:

authorization bypasses
configuration changes
unexpected truthy flags
denial of service
template injection paths
broken application assumptions
security control bypasses

The exact impact depends on what polluted properties influence.

Unsafe Merge Pattern

Naive deep merge code is risky.

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;
}

This code accepts any key and walks inherited properties because it uses for...in.

Dangerous Keys

Common dangerous keys include:

__proto__
constructor
prototype

Different vulnerable paths use different combinations.

Blocking only one key may be insufficient.

Safer Key Iteration

Prefer own keys over inherited enumeration.

for (const key of Object.keys(source)) {
  // own enumerable string keys only
}

This avoids walking inherited properties from source.

Still, key validation is required.

Key Validation

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

function isSafeKey(key) {
  return !blockedKeys.has(key);
}

For highly sensitive code, an allowlist is often safer than a blocklist.

const allowedKeys = new Set(["theme", "locale", "density"]);

Object.create(null)

Objects created with Object.create(null) do not inherit from Object.prototype.

const dictionary = Object.create(null);
dictionary["__proto__"] = "value";

Here __proto__ is a normal key, not an inherited accessor.

This is useful for dictionaries, but such objects do not have normal object methods like hasOwnProperty.

Prefer Map for Arbitrary Keys

When keys come from users or external systems, Map is often clearer.

const dictionary = new Map();
dictionary.set(userProvidedKey, value);

Map avoids accidental prototype key behavior and supports arbitrary key values.

Safe Merge Direction

Avoid blind recursive merges of untrusted data into important objects.

Instead, parse and map trusted fields explicitly.

function parsePreferences(input) {
  return {
    theme: input.theme === "dark" ? "dark" : "light",
    density: input.density === "compact" ? "compact" : "comfortable",
  };
}

This turns unknown input into a known shape.

Frontend Impact

Frontend apps may parse query strings, local storage, feature flags, theme config, JSON imports, or API responses.

Pollution may not always become remote code execution, but it can still break UI logic, feature gates, security assumptions, and data rendering.

Dependency Risk

Prototype pollution often appears in utility packages:

deep merge
path setters
query string parsers
YAML parsers
configuration loaders
template helpers

Keep dependencies patched and audit packages that process untrusted object paths.

Common Mistakes

A common mistake is saying frontend prototype pollution is harmless because it runs in the browser. Browser code still handles tokens, permissions, feature flags, and user trust.

Another mistake is relying on UI-only authorization flags. Server authorization must never trust client-side polluted state.

Senior Trade-Offs

Safe object merging is about narrowing data.

Use explicit schemas at trust boundaries. Avoid merging untrusted input into application config. Prefer allowlists for security-sensitive objects.

Performance and convenience do not justify unclear object mutation at boundaries.

Code or Design Example

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

function safeAssign(target, source) {
  for (const key of Object.keys(source)) {
    if (blockedKeys.has(key)) {
      continue;
    }

    target[key] = source[key];
  }

  return target;
}

This is safer than a blind merge, but still only appropriate for simple flat data. Nested input should be validated with a schema.

Debugging Workflow

When investigating prototype pollution:

search for deep merge and path setter utilities
inspect query parsing and config loading
test dangerous keys in controlled development
check Object.prototype for unexpected properties
replace for...in with own-key iteration where needed
add schema validation at input boundaries
patch vulnerable dependencies
verify server authorization does not trust client flags

Interview Framing

Explain the attack shape first: untrusted key writes to prototype-related paths. Then give unsafe merge and safe parsing examples.

Revision Notes

Prototype pollution is usually caused by unsafe object writes from untrusted input. Validate keys and avoid blind deep merges.

Key Takeaways

JavaScript object flexibility is powerful, but untrusted keys must be treated as data, not as instructions for object structure.

Follow-Up Questions

  1. What is prototype pollution?
  2. Which keys are dangerous?
  3. Why is unsafe deep merge risky?
  4. Why can for...in be dangerous?
  5. How does Object.create(null) help?
  6. When is Map better than object?
  7. Why are allowlists safer than blocklists?
  8. Which dependencies commonly have this risk?
  9. Can frontend pollution matter?
  10. How do you defend at trust boundaries?

How I Would Answer This In A Real Interview

I would define prototype pollution as attacker-controlled writes into prototype paths. Then I would show how unsafe deep merge causes it, name dangerous keys, and recommend schema validation, safe libraries, own-key iteration, Object.create(null), Map, and server-side authorization.