Join our Newsletter — 33% off our NHI Course
Home› Guides› Prototype Pollution Guide: JavaScript Object Safety and Prevention
Guide Web & API Security

Prototype Pollution Guide: JavaScript Object Safety and Prevention

PDF for newsletter subscribers · free to read online

← All guides
By Lalit Choda, NHI Mgmt Group Updated 24 October 2026 11 min read
On this page

In JavaScript, a single write to a shared prototype changes the behaviour of every object in the process, including objects the attacker never touched. Prototype pollution, catalogued as CWE-1321, turns ordinary code patterns such as recursive merges, deep defaults and path-based setters into a way to inject properties that other code later trusts. It has affected some of the most widely deployed libraries, including jQuery before 3.4.0 and lodash before 4.17.12, and in 2026 the CISA Known Exploited Vulnerabilities (KEV) catalogue added a prototype pollution flaw in Adobe Acrobat and Reader. This guide explains the mechanism conceptually, where it appears in browser and Node.js code, how to detect it, and how to design objects that cannot be polluted. It is for JavaScript and TypeScript developers, application security engineers and code reviewers, and contains no exploit strings.

Key takeaways

  • It is a two-phase bug. MDN describes a pollution step, where input adds a property to a prototype, and an exploitation step, where unrelated code reads that property and misbehaves.
  • Unsafe object handling is the source. Recursive merge, clone, deep-defaults and path setters that copy attacker-chosen keys are the classic entry points, as the CWE-1321 example CVEs show.
  • Null-prototype objects and Map remove the problem by design. CWE-1321 rates Object.create(null) as highly effective and Map as moderately effective; OWASP ASVS 5.0 V15.3.6 asks for Set and Map over object literals.
  • Key blocklists are a fallback, not a fix. Blocking __proto__ alone misses constructor.prototype; MDN and OWASP both say no single defence is sufficient.
  • Dependencies carry most of the risk. Much real exposure comes from vulnerable utility libraries, so software composition analysis and fast upgrades matter as much as your own code.

How the attack works

When JavaScript looks up a property that an object does not have, it walks the prototype chain up to Object.prototype. Prototypes are ordinary, mutable objects at runtime. CWE-1321 defines the weakness as accepting input that sets or updates object attributes without properly controlling modifications to the object prototype. MITRE places it under CWE-915 (improperly controlled modification of dynamically determined object attributes) and notes it can precede CWE-471, modification of assumed-immutable data.

  • Pollution: code that assigns properties using keys derived from input, in the pattern MDN summarises as nested dynamic assignment, can be steered to write to a prototype instead of to the intended object. Two paths matter: the __proto__ accessor, and the constructor.prototype route, which still works when the accessor is disabled.
  • Exploitation: application or library code later reads a property it expects to be absent, such as an option, a flag or a configuration value, and receives the polluted value through inheritance. MDN calls this a data-only attack: the attacker never runs code directly, they supply data that the application processes.
  • JSON.parse is not the culprit on its own: MDN explains that JSON treats __proto__ as an ordinary key, so parsing is harmless, but merging the parsed result with Object.assign() or a for...in copy loop invokes the setter. Object spread syntax does not invoke setters.
  • Gadgets: the code that turns a polluted property into impact. Without a gadget, pollution may only cause errors; with one, it can bypass checks or reach script execution.

Where it shows up

Source patternTypical locationKnown examples
Recursive merge or extendConfiguration loaders, option handling, state managementCVE-2019-11358: jQuery before 3.4.0, jQuery.extend(true, ...); CVE-2018-3721 (merge, per CWE-1321)
Recursive default settingUtility libraries filling in missing optionsCVE-2019-10744: lodash before 4.17.12, defaultsDeep
Dot or bracket path settersForm handling, query string parsers that build nested objects, ORMsCVE-2020-8203 (dot-separated path, per CWE-1321)
Query string and hash parsing in the browserSingle-page apps parsing location.search into deep objectsMDN notes libraries are more exposed than apps because they cannot allowlist keys
Copying records by field nameBack-end handlers copying database or request fields onto objectsMDN server-side example
Embedded JavaScript enginesDocument readers and desktop products that run scriptsCVE-2026-34621, Adobe Acrobat and Reader (KEV, added 13 April 2026)

Client-side vs server-side

  • Client-side: MDN lists configuration objects passed to browser APIs as gadgets, for example fetch() options whose method or body can be altered, iframe srcdoc handling and HTML sanitiser configuration. The usual outcome is DOM-based cross-site scripting. See the Cross-Site Scripting (XSS) Prevention Guide and the Client-Side Security Guide.
  • Server-side (Node.js): the whole process shares one Object.prototype, so a pollution in one request can affect every later request until the process restarts. Gadgets include authorisation checks that treat a missing property as false, and options passed to template engines or child process helpers. See the Node.js Security Guide.

Impact

ImpactHow it arisesSeverity driver
Authorisation bypassA check such as "is this user an admin" falls through to an inherited polluted valueWhether access decisions read optional properties without own-property checks
Data tamperingCWE-1321: attributes added or replaced on every object, including hasOwnProperty, toString or valueOfHow widely the polluted name is read
Denial of serviceCWE-1321: overriding attributes with incompatible types crashes the applicationLong-lived Node.js processes amplify a single request
Script execution in the browserA gadget writes a polluted value into HTML, a sanitiser setting or a script sourcePresence of DOM gadgets; Content Security Policy strength
Code execution on the serverA gadget passes polluted options to code that spawns processes or compiles templatesLibrary gadgets present in the dependency tree

NVD scores reflect this range: 6.1 for the jQuery issue (user interaction, scope change) and 9.1 for the lodash issue.

Detection

  • Static analysis: CWE-1321 rates SAST as highly effective. Use taint rules that trace request data, URL parameters and parsed JSON into recursive merge, assignment-by-path and computed property writes.
  • Software composition analysis: scan lockfiles for packages with CWE-1321 advisories and upgrade promptly; many fixes are minor version bumps. See the Software Supply Chain Security Guide.
  • Code review cues: computed property writes with two levels of input-derived keys, home-grown deep merge or clone helpers, for...in loops without own-property checks, and authorisation reads of optional fields.
  • Dynamic testing: include prototype pollution checks for query string, JSON body and hash fragment handling in your DAST and penetration test scope. See the Web Application Security Testing Guide.
  • Runtime signals: in Node.js, --disable-proto=throw makes any __proto__ access raise ERR_PROTO_ACCESS, which turns attempts into loggable errors; watch for spikes in that error code and for unexplained crashes across many requests.

Metrics

  • Dependency exposure: number of services with a known CWE-1321 vulnerable package, and median days to upgrade.
  • Unsafe helper count: number of custom deep merge, clone or path-set helpers not using null-prototype targets or key validation.
  • Schema coverage: percentage of API endpoints validating bodies with a schema that rejects additional properties.
  • SAST findings: open CWE-1321 findings and their age.
  • Runtime hardening: percentage of Node.js services started with --disable-proto.

Prevention and hardening

Controls compared

ControlEffectLimits (per CWE-1321, OWASP and MDN)
Map and SetLookups return only stored entries, so polluted prototypes cannot leak inOnly for key-value stores; APIs such as fetch() still need plain objects
Object.create(null) or { __proto__: null } literalNo inherited prototype, so nothing to pollute and nothing inheritedMust be chosen deliberately; some APIs expect normal objects; the obj.__proto__ accessor is not safe
Schema validation (for example Ajv or Zod with additionalProperties: false)Rejects unexpected keys at the boundaryCWE-1321 rates effectiveness as limited on its own; dynamic assignment still needs key checks
Key validationReject keys that resolve to the prototype: __proto__, constructor, prototypeBlocklists are a fallback; prefer allowlists of expected keys
Object.hasOwn() and Object.keys()Read or iterate own properties onlyMust be applied at every access site; for...in still walks the chain
Object.freeze(Object.prototype) or SES lockdownBuilt-in prototypes cannot be changedShallow; polyfills must load first; failed writes are silent in sloppy mode; test library compatibility
Node.js --disable-proto=delete|throwRemoves or traps the __proto__ accessor (added v12.17.0 and v13.12.0)Node-only, defence in depth; constructor.prototype remains reachable

Decision guidance

  • Library authors: you cannot allowlist your callers' keys, so build merge and set targets with null prototypes, skip prototype-resolving keys, and avoid relying on users freezing globals. CWE-1321 recommends approaches other than freezing for libraries.
  • Application teams: validate at the boundary with strict schemas, use Map for dictionaries keyed by user data, and replace custom deep merge helpers with maintained, patched ones.
  • Freezing: best suited to Node.js services and controlled front ends where you own the load order. Pilot it, because some libraries modify built-in prototypes and will break.
  • Small teams: start with dependency upgrades, a lint rule against custom deep merges and --disable-proto=throw. Large organisations should add SAST taint rules and a shared safe-object utility package.
  • TypeScript: types are erased at runtime and do not stop pollution; runtime validation is still required.

Rollout plan

  1. Patch dependencies: run SCA across all repositories and upgrade packages with CWE-1321 advisories, starting with internet-facing services.
  2. Find sources: search for custom merge, clone, defaults and path-set helpers, and for query parsers configured to build nested objects.
  3. Fix sources: switch targets to null-prototype objects or Map, add key allowlists, and add unit tests asserting that prototype-resolving keys do not change Object.prototype.
  4. Harden the boundary: add strict schema validation to request bodies.
  5. Add runtime controls in audit first: start Node.js services in staging with --disable-proto=throw and log ERR_PROTO_ACCESS; fix legitimate uses, then promote to production. Roll back by removing the flag from the start command if a dependency fails.
  6. Pilot freezing on one service, then expand; keep the change behind a start-up flag so it can be disabled quickly.
  7. Lock in: make SAST and SCA findings blocking in CI. See the Secure Coding Guide.

Framework mapping

FrameworkReferenceRelevance
CWECWE-1321 (child of CWE-915 and CWE-913)Weakness definition
OWASP ASVS 5.0V15.3.6JavaScript written to prevent prototype pollution, for example with Set or Map
MITRE CAPECCAPEC-1, CAPEC-77, CAPEC-180 (listed by CWE-1321)Related attack patterns
MITRE ATT&CKT1190 Exploit Public-Facing Application; T1059.007 JavaScriptExploitation of internet-facing services; script execution outcomes
NIST SP 800-53 Rev. 5SI-10, SA-11, SI-2, RA-5, SR-3Input validation, developer testing, flaw remediation, scanning, supply chain controls
NIST CSF 2.0PR.PS-06, ID.RA-01Secure development practices; vulnerability identification

Common failure patterns

  • Blocking only __proto__: the constructor.prototype path remains open, as both MDN and OWASP point out.
  • Home-grown deep merge: a short helper copied from a forum copies every key recursively into a plain object.
  • Stale transitive dependencies: the direct dependency was upgraded but an old copy of a vulnerable utility library remains deeper in the tree.
  • Authorisation reading optional fields: access checks that treat "not present" as "false" without Object.hasOwn() become gadgets.
  • Relying on TypeScript types or JSON.parse safety: neither stops the merge step that actually pollutes.
  • Freezing after polyfills fail: freezing too early breaks polyfills, and teams remove the freeze entirely rather than fixing load order.

Practitioner checklist

  • Run SCA on every JavaScript repository and upgrade packages with CWE-1321 advisories, including transitive ones.
  • Replace custom deep merge, clone and path-set helpers with patched libraries or null-prototype implementations.
  • Use Map and Set for collections keyed by user data, per ASVS V15.3.6.
  • Create dictionaries and merge targets with Object.create(null).
  • Validate request bodies with schemas that reject additional properties, and allowlist keys in dynamic assignment.
  • Use Object.hasOwn() in authorisation and configuration reads.
  • Start Node.js services with --disable-proto=throw after an audit period, and monitor ERR_PROTO_ACCESS.
  • Pilot freezing built-in prototypes where you control load order.
  • Add SAST taint rules for CWE-1321 and unit tests for every merge helper.

Standards and references

Related NHI Mgmt Group resources: Web Application Attacks Guide · Node.js Security Guide · Malicious Package and Dependency Confusion Guide · Insecure Deserialisation Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 24 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.