# Prototype pollution via recursive merge

Untrusted keys reaching merge/deep-set helpers, then a sink reading the polluted property name

_Area: Web injection and code execution. Seen in 13 disclosed reports; 6 confirmed bounties, median $3,000, range $200-$10,000; latest 2026-10-22. Updated 2026-10-23._

## How it works
A merge, deep-set, or query/deparam parser copies attacker-supplied keys into a target object without rejecting `__proto__`, `constructor`, or `prototype`. Writing such a key walks up to the shared prototype object and sets a property there, so every object in the process now carries the attacker's value. The broken assumption is that keys from documents, URLs, config files, or DSL directives are just data; in fact they are paths into the object graph. The pollution may be read immediately by a sink in the same code path [E3](https://bugbountylibrary.com/r/h1%3A986386.md)[E5](https://bugbountylibrary.com/r/blog%3Ajoaxcar-com-blog-2024-01-26-hunting-for-prototype-pollution-gadgets-in-jquery-intigriti-0124-challenge.md), or lie dormant until unrelated code reads that property name as config, HTML, or code [E1](https://bugbountylibrary.com/r/h1%3A852613.md)[E2](https://bugbountylibrary.com/r/h1%3A1280002.md).

## Where it shows up
- `_.set`, `_.merge`, deep-extend, or hand-rolled recursive merge on saved objects, config, or document fields [E1](https://bugbountylibrary.com/r/h1%3A852613.md)[E6](https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-supply-chain-pollution-hunting-a-16-million-download-week-npm-package.md)
- URL query/hash parsing into objects, including third-party embed scripts [E3](https://bugbountylibrary.com/r/h1%3A986386.md)[E7](https://bugbountylibrary.com/r/bc%3A792f3c12-0d77-496a-a9cc-39eda2bf9f3f.md)[E8](https://bugbountylibrary.com/r/h1%3A998398.md)
- Diagram/DSL config merges such as Mermaid `%%{init:...}%%` JSON [E2](https://bugbountylibrary.com/r/h1%3A1280002.md)
- Ruby recursive merge helpers calling `instance_variable_set`/`attr_accessor` and recursing via `respond_to?`/`public_send` [E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2024-10-02-class-pollution-ruby-html.md)
- Dotted-path/config parsers that split keys and assign nested properties without a blacklist [E6](https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-supply-chain-pollution-hunting-a-16-million-download-week-npm-package.md)
- Downstream consumers of names like `sourceURL`, `template`, `innerHTML`, `selector` [E1](https://bugbountylibrary.com/r/h1%3A852613.md)[E2](https://bugbountylibrary.com/r/h1%3A1280002.md)[E3](https://bugbountylibrary.com/r/h1%3A986386.md)[E5](https://bugbountylibrary.com/r/blog%3Ajoaxcar-com-blog-2024-01-26-hunting-for-prototype-pollution-gadgets-in-jquery-intigriti-0124-challenge.md)

## How to review
Trace every key that reaches a merge from an untrusted source. The invariant: no attacker-controlled key may name `__proto__`, `constructor`, or `prototype` (in Ruby, `class`, `superclass`, `subclasses`), and merges must not recurse into class-level state. Confirm the filter runs on the key path, not just the top level. Then work the sink side: grep the same bundle for reads of polluted property names and for sinks (eval, template, `child_process`, iframe srcdoc, innerHTML, jQuery()). Pollution alone is low impact; pollution plus a sink is the bug.

## Variants seen
- Saved-object telemetry keys polluting `sourceURL` consumed as JS [E1](https://bugbountylibrary.com/r/h1%3A852613.md)
- Mermaid directive polluting `template` used as iframe srcdoc [E2](https://bugbountylibrary.com/r/h1%3A1280002.md)
- Third-party embed parsing `location` into `Object.prototype.innerHTML` [E3](https://bugbountylibrary.com/r/h1%3A986386.md)
- Ruby class pollution escaping into class variables, enabling auth bypass and RCE [E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2024-10-02-class-pollution-ruby-html.md)
- jQuery gadgets via `attr({})`/`on()` coercing polluted selector arrays to HTML [E5](https://bugbountylibrary.com/r/blog%3Ajoaxcar-com-blog-2024-01-26-hunting-for-prototype-pollution-gadgets-in-jquery-intigriti-0124-challenge.md)
- INI parser accepting a `[__proto__]` section [E6](https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-supply-chain-pollution-hunting-a-16-million-download-week-npm-package.md)
- Backbone query-param parser polluting display values into DOM [E7](https://bugbountylibrary.com/r/bc%3A792f3c12-0d77-496a-a9cc-39eda2bf9f3f.md)
- `deparam` on `location.hash` chained with an eval gadget [E8](https://bugbountylibrary.com/r/h1%3A998398.md)

## Impact and severity
Range from HTML injection and DoS to stored XSS and remote code execution on the host [E1](https://bugbountylibrary.com/r/h1%3A852613.md)[E2](https://bugbountylibrary.com/r/h1%3A1280002.md)[E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2024-10-02-class-pollution-ruby-html.md). Severity rises when the polluted property feeds a code or process sink, when the source is a stored document any writer can set, and when CSP is the only barrier [E7](https://bugbountylibrary.com/r/bc%3A792f3c12-0d77-496a-a9cc-39eda2bf9f3f.md).

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A852613.md
- E2: https://bugbountylibrary.com/r/h1%3A1280002.md
- E3: https://bugbountylibrary.com/r/h1%3A986386.md
- E4: https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2024-10-02-class-pollution-ruby-html.md
- E5: https://bugbountylibrary.com/r/blog%3Ajoaxcar-com-blog-2024-01-26-hunting-for-prototype-pollution-gadgets-in-jquery-intigriti-0124-challenge.md
- E6: https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-supply-chain-pollution-hunting-a-16-million-download-week-npm-package.md
- E7: https://bugbountylibrary.com/r/bc%3A792f3c12-0d77-496a-a9cc-39eda2bf9f3f.md
- E8: https://bugbountylibrary.com/r/h1%3A998398.md
