DOM-based XSS is a cross-site scripting vulnerability that lives entirely in client-side code. The page's own JavaScript reads attacker-controllable input — most often from the URL — and writes it into the DOM through an unsafe sink like innerHTML, executing the attacker's script. Unlike reflected or stored XSS, the malicious payload may never appear in any server response.
How does a DOM XSS attack work?
The vulnerability is a data flow from a source the attacker controls to a sink that interprets text as code or markup:
// vulnerable: fragment flows straight into a sink
const name = decodeURIComponent(location.hash.slice(1));
document.querySelector("#welcome").innerHTML = "Hello " + name;
// https://example.com/#<img src=x onerror=stealCookies()>
Common sources: location.hash, location.search, document.referrer, window.name, postMessage data, and client-side storage. Common sinks: innerHTML/outerHTML, document.write, eval and Function, jQuery's .html(), and attribute writes that create handlers or URLs (onclick, href with javascript:).
Because the URL fragment is never sent to the server, a payload after # can exploit the page while the server sees a perfectly normal request.
Reflected vs stored vs DOM-based XSS
| Reflected XSS | Stored XSS | DOM-based XSS | |
|---|---|---|---|
| Where the payload lives | In the request, echoed by the server | In the server's database | In client-side input (often the URL fragment) |
| Server response contains payload? | ✓ | ✓ | Often ✗ |
| Visible to WAFs / server logs | Usually | Usually | Often not |
| Fixed where | Server-side output encoding | Server-side sanitization + encoding | Client-side code: safe sinks, sanitization |
Why DOM XSS is easy to miss
Server-side defenses inspect requests and responses; DOM XSS can bypass both. Frameworks reduce the risk — React and friends escape by default — but dangerouslySetInnerHTML, template misuse, and the third-party scripts on the page reintroduce it. And a modern page runs dozens of scripts you did not write: an unsafe sink in any of them is an unsafe sink on your page. That composition problem is the core of client side security.
How to prevent and detect it
- Prefer safe sinks:
textContentoverinnerHTML; avoidevaland string-built handlers entirely. - Sanitize when HTML is unavoidable (DOMPurify or the emerging Sanitizer API), and adopt Trusted Types where supported — it turns unsafe sink writes into policy violations.
- Deploy a strict Content Security Policy with nonces; it blocks many payload styles, though not the whole class.
- Watch runtime behavior: exploitation ultimately manifests as scripts doing things they should not — new outbound requests, DOM writes into payment forms, unexpected listeners. cside's client-side monitoring observes real sessions at the browser layer, which is the only place a DOM-only attack is visible at all.







