# DOM XSS via HTML parsing sinks

Untrusted strings reaching innerHTML, jQuery html/append, or selectors before encoding

_Area: Web injection and code execution. Seen in 20 disclosed reports; 8 confirmed bounties, median $750, range $300-$8,000; latest 2026-10-22. Updated 2026-10-23._

## How it works
A string from an untrusted source (URL param, cookie, postMessage field, stored record, drag-and-drop payload, filename, caption cue) is assigned to `innerHTML`, a jQuery `html`/`append`, or concatenated into a jQuery selector. The browser parses that string as HTML at assignment time, so markup and event handlers in the data become live DOM. The broken assumption is "we only show text" or "we sanitize later": parsing happens on assignment, and handlers like `img onerror` fire during parsing even on detached elements [E1](https://bugbountylibrary.com/r/blog%3Ablogs-opera-com-security-2021-09-8000-bug-bounty-highlight-xss-to-rce-in-the-opera-browser.md)[E7](https://bugbountylibrary.com/r/ghsa%3AGHSA-xhq9-whgq-49j5.md). A related sub-mechanism is a recursion-guard sentinel: a helper skips escaping any object that already carries a `stripping` flag, and attacker-supplied JSON can pre-set that flag to opt out of sanitization [E5](https://bugbountylibrary.com/r/h1%3A246794.md).

## Where it shows up
- `innerHTML`, `outerHTML`, `insertAdjacentHTML`, jQuery `.html(`/`.append(` fed from params, cookies, postMessage fields, stored data, or caption cue text [E1](https://bugbountylibrary.com/r/blog%3Ablogs-opera-com-security-2021-09-8000-bug-bounty-highlight-xss-to-rce-in-the-opera-browser.md)[E3](https://bugbountylibrary.com/r/h1%3A2371019.md)[E6](https://bugbountylibrary.com/r/ghsa%3AGHSA-m6c8-jcw2-5r25.md)[E7](https://bugbountylibrary.com/r/ghsa%3AGHSA-xhq9-whgq-49j5.md)
- "Strip HTML" helpers that assign to `innerHTML` then read `textContent` [E7](https://bugbountylibrary.com/r/ghsa%3AGHSA-xhq9-whgq-49j5.md)
- Sanitizer (DOMPurify) called after assignment, or on a detached node [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md)
- Escaping helpers that skip objects carrying a sentinel property from untrusted JSON [E5](https://bugbountylibrary.com/r/h1%3A246794.md)
- Sinks inside an authenticated form carrying a CSRF token [E2](https://bugbountylibrary.com/r/h1%3A3608199.md)
- Sanitizer hooks that detach subtrees, leaving handlers armed [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md)
- Error responses reflected into `innerHTML` [E8](https://bugbountylibrary.com/r/h1%3A2246576.md)

## How to review
Trace every sink to its source. The invariant: untrusted data must be encoded or sanitized before it reaches a parsing sink, and must never be concatenated into a selector. Default fix is `textContent` (or attribute setters) instead of `innerHTML` [E2](https://bugbountylibrary.com/r/h1%3A3608199.md). Grep for `innerHTML`, `.html(`, `insertAdjacentHTML`, `append(`; for each, confirm sanitization runs on the string before assignment, not on the resulting DOM [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md)[E7](https://bugbountylibrary.com/r/ghsa%3AGHSA-xhq9-whgq-49j5.md). For selectors, confirm values are escaped or passed as data. For postMessage, confirm the listener validates origin and encodes fields [E3](https://bugbountylibrary.com/r/h1%3A2371019.md). For sanitizer hooks, confirm detached subtrees are neutralized, not just the current node [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md). For escaping helpers, confirm no input-supplied flag can skip escaping [E5](https://bugbountylibrary.com/r/h1%3A246794.md).

## Variants seen
- Drag-and-drop `text/html` into detached span innerHTML [E1](https://bugbountylibrary.com/r/blog%3Ablogs-opera-com-security-2021-09-8000-bug-bounty-highlight-xss-to-rce-in-the-opera-browser.md)
- Filename preview via innerHTML inside authenticated form, formaction submit injection [E2](https://bugbountylibrary.com/r/h1%3A3608199.md)
- postMessage avatar_URL into innerHTML on a trusted origin [E3](https://bugbountylibrary.com/r/h1%3A2371019.md)
- innerHTML before DOMPurify on detached div [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md)
- Sentinel `stripping` property in attacker JSON bypasses escaping [E5](https://bugbountylibrary.com/r/h1%3A246794.md)
- Sanitizer hook detaching a parent/sibling leaves subtree handlers armed [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md)
- Caption cue text appended via `innerHTML +=` in a media player [E6](https://bugbountylibrary.com/r/ghsa%3AGHSA-m6c8-jcw2-5r25.md)
- innerHTML-based HTML stripping fires img onerror [E7](https://bugbountylibrary.com/r/ghsa%3AGHSA-xhq9-whgq-49j5.md)
- check_pattern error reflected into innerHTML, CSP bypassed via script gadgets [E8](https://bugbountylibrary.com/r/h1%3A2246576.md)

## Why fixes fail
DOMPurify's earlier fix only checked whether the current node lost its parent; removing an ancestor or unvisited sibling left the subtree unneutralized and absent from `removed` [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md).

## Impact and severity
Ranged from stored XSS and cookie theft [E3](https://bugbountylibrary.com/r/h1%3A2371019.md) to account takeover via CSRF-token inheritance [E2](https://bugbountylibrary.com/r/h1%3A3608199.md). Severity rose when XSS reached privileged extension APIs or file-write primitives [E1](https://bugbountylibrary.com/r/blog%3Ablogs-opera-com-security-2021-09-8000-bug-bounty-highlight-xss-to-rce-in-the-opera-browser.md), when a sanitizer left handlers armed [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md), or when CSP was bypassed via script gadgets [E8](https://bugbountylibrary.com/r/h1%3A2246576.md). RCE outcomes [E1](https://bugbountylibrary.com/r/blog%3Ablogs-opera-com-security-2021-09-8000-bug-bounty-highlight-xss-to-rce-in-the-opera-browser.md) are conditional on chained primitives.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Ablogs-opera-com-security-2021-09-8000-bug-bounty-highlight-xss-to-rce-in-the-opera-browser.md
- E2: https://bugbountylibrary.com/r/h1%3A3608199.md
- E3: https://bugbountylibrary.com/r/h1%3A2371019.md
- E4: https://bugbountylibrary.com/r/ghsa%3AGHSA-mrv2-3pqr-j5jr.md
- E5: https://bugbountylibrary.com/r/h1%3A246794.md
- E6: https://bugbountylibrary.com/r/ghsa%3AGHSA-m6c8-jcw2-5r25.md
- E7: https://bugbountylibrary.com/r/ghsa%3AGHSA-xhq9-whgq-49j5.md
- E8: https://bugbountylibrary.com/r/h1%3A2246576.md
