Prototype pollution: defensive guidance (JavaScript)¶
Prototype pollution bugs happen when attacker-controlled keys can write into an object’s prototype chain (e.g. __proto__, constructor.prototype). Impact ranges from weird app behavior to auth bypasses and, in some cases, RCE depending on downstream “sinks”.
When to worry¶
Treat as high risk when you have any of the following:
- You accept untrusted JSON and then:
- deep-merge it into config/state objects
- apply it as patch/update operations
- use it to construct options passed to security-sensitive libraries
- You use “dot-path” setters (e.g.
a.b.c) on untrusted input. - You use utility libraries that historically had prototype pollution issues (deep merge / deep set / querystring parsing).
Practical prevention checklist¶
1) Block dangerous keys everywhere you accept object paths¶
If you accept arbitrary keys (or dot-paths), reject any segment equal to:
__proto__prototypeconstructor
Also consider blocking toString, valueOf, __defineGetter__, __defineSetter__ in high-risk contexts.
2) Avoid deep-merging untrusted objects¶
Prefer explicit allowlists:
- Parse input → validate schema → copy only known fields
- If you must merge, merge into a fresh object with a null prototype (see below)
3) Use null-prototype maps for “dictionary” objects¶
For key/value maps populated from untrusted data:
This prevents inherited properties from being interpreted as user data.
4) Use schema validation, not “best effort” sanitization¶
Use a schema validator (e.g., Zod/Ajv/Joi) and require:
additionalProperties: falsewhere possible- types and bounds on values
- explicit key allowlists for records/dicts
5) Treat library patches as insufficient unless tests prove it¶
Some fixes can be bypassed by monkeypatching built-ins (e.g., Object.prototype.hasOwnProperty, String.prototype.indexOf, String.prototype.includes) in hostile environments.
This shows up in real-world prototype pollution advisories: a library may “block” keys by checking for forbidden substrings, but if an attacker can influence runtime (plugins, templating, multi-tenant JS execution, or any eval/Function sink), they may be able to override built-ins and bypass those checks.
Defensive posture:
- Don’t rely on a single check like
hasOwnPropertyorstr.includes('__proto__')for security - Validate structure with schemas/allowlists (preferred) instead of substring blacklists
- Add regression tests for
__proto__andconstructor.prototypepayloads - Run SAST + unit tests that include pollution probes
Detection / verification probes¶
Add a CI test that fails if a pollution primitive is possible:
function assertNotPolluted() {
if (({}).polluted !== undefined) throw new Error('Prototype polluted');
if (({}).isAdmin !== undefined) throw new Error('Prototype polluted');
}
assertNotPolluted();
// exercise your parser/merge/setter on attacker-controlled inputs
assertNotPolluted();
Example payload families to test:
{"__proto__":{"polluted":"yes"}}- path-based setters:
"__proto__.polluted","constructor.prototype.polluted"
Incident response notes¶
If you suspect prototype pollution in production:
- Identify the entry point (JSON body, query parser, webhooks, config import, etc.)
- Search logs for keys containing
__proto__,constructor,prototype - Assume secondary impact until proven otherwise (authz decisions, template rendering, command execution, SSRF settings)
References¶
- GitHub Advisory (example):
deephasprototype pollution viaconstructor.prototype/__proto__(CVE-2026-25047) - GitHub Advisory (example):
makerjs.extendObjectunsafe property copying can enable prototype pollution-style attacks (CVE-2026-24888)