Security Defence
JavaScript Prototype Pollution: Review Config Merges and Dynamic Keys
Prevent prototype pollution with reviews of dynamic writes, explicit schemas, and merge tests instead of assuming parsed JSON is safe application configuration.
In this article
Parsing JSON successfully doesn't make it safe to merge into application configuration. A program may accept an ordinary object, walk user-controlled keys, and write them into structures whose inherited properties affect later behaviour. The dangerous step can happen after parsing, far from the input endpoint.
JavaScript prototype pollution review follows dynamic property writes and merge operations. The goal is to keep untrusted keys from changing shared object behaviour. This guide uses harmless examples and defensive checks rather than an exploit procedure.
Distinguish parsing from mutation
JSON.parse converts a JSON representation into values. It doesn't automatically validate that those values are suitable for your configuration schema or safe for a later recursive merge. A syntactically valid object can still contain unexpected keys and types.
MDN's prototype-pollution explanation describes the role of dynamic property assignment and inherited properties. Inspect the operation that consumes the data, not just the parser that accepted it.
Use JSON Formatter with a synthetic configuration to see its structure. The tool cannot determine whether your merge library handles every key safely. It validates syntax, not the application's object-mutation policy.
Find the dynamic-key paths
Review recursive merge helpers, query-string parsers, nested form handlers, and code that assigns values through user-supplied paths. Include third-party libraries and administrative configuration imports. The input may arrive through more than one route.
Ask which keys are actually necessary. If the product supports only theme, pageSize, and notifications, it doesn't need an arbitrary object path writer. A small explicit schema is often easier to secure and test than a general configuration language.
The OWASP prototype-pollution prevention guidance provides defensive patterns. Apply them with your runtime and library versions rather than copying one blacklist and assuming the problem is solved everywhere.
Prefer explicit construction
Build the accepted configuration from known fields instead of merging every supplied property. Validate types and ranges, reject unsupported fields where appropriate, and assign defaults deliberately. This also makes the product's settings easier to document.
For example, a fictional settings handler might construct:
const settings = {
theme: input.theme === "dark" ? "dark" : "light",
pageSize: Number.isInteger(input.pageSize)
&& input.pageSize >= 1 && input.pageSize <= 100
? input.pageSize : 20
};This illustrates explicit field selection; it isn't a complete security validator. Add the application's supported error behaviour and other schema checks. Don't silently accept invalid inputs if the contract requires a clear rejection.
Keep the allowed fields close to the feature that uses them. If a new setting is introduced, require an explicit schema and consumer change together. This prevents a general merge helper from granting accidental support to every property a caller supplies, and makes a code review's scope much easier to understand.
Choose data structures intentionally
For arbitrary key-value data, consider structures designed for that purpose, such as Map, where appropriate. Null-prototype objects can also remove an ordinary prototype chain, but they affect available methods and need compatible handling throughout the code.
Use supported own-property checks when distinguishing supplied fields from inherited values. Don't assume a method read from an untrusted object is safe to call. Schema validation and safe construction still matter even when the chosen structure reduces one class of risk.
Avoid making broad claims that object spread, one library option, or one runtime flag fixes every path. Review the exact semantics of the operations your application performs. Different assignment and merge techniques can behave differently.
Test before and after the merge
Use a controlled test environment and synthetic inputs containing unsupported, nested, and unusual keys. Check that they are rejected or treated as ordinary isolated data according to the contract. Confirm that unrelated objects retain their expected behaviour.
Also test arrays, null values, repeated configuration layers, and deeply nested structures. Bound nesting and size so the validator or merge routine cannot become an unbounded resource sink. A safe key policy shouldn't hide a separate denial-of-service problem.
Use JavaScript Formatter to review sanitised helper code and Text Diff to compare revisions. Neither executes security tests. Run the checks in your own environment and inspect the actual resulting objects.
Check the negative result as well as the happy path. A rejected configuration should leave the previously accepted settings intact. If validation mutates shared state before reporting an error, the failure response can conceal a lasting change that later requests inherit.
Keep dependencies and configuration changes reviewable
Pin supported library versions and review security advisories through official sources. If a merge dependency changes, rerun the defensive fixtures rather than assuming a version increase preserves your intended behaviour.
Check places where configuration controls network access, logging, permissions, or feature flags. Unexpected properties can have different consequences depending on the consumer. Prioritise paths that influence sensitive operations, not only those easiest to find.
For a service accepting outbound URLs, see SSRF prevention. Configuration integrity and destination validation are separate controls; an apparently safe settings parser doesn't establish a safe network policy.
Conclusion
Valid JSON is only the start of configuration handling. Follow dynamic writes, constrain the schema, construct accepted settings explicitly, and test that unrelated objects remain unchanged. Prototype-pollution prevention becomes more dependable when the application avoids unnecessary general-purpose mutation in the first place.