← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

DOM-Based XSS

security CWE-79 OWASP A3:2021 CVSS 6.1 ES5 Intermediate
debt(d5/e3/b3/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The term's detection_hints lists semgrep and eslint with code_pattern matches for innerHTML = location.hash/search. These tools can catch common patterns, but DOM XSS flows through dynamic runtime behavior and indirect data flows (e.g., chained variable assignments) that static tools miss — making it closer to d5 than d3 (default linter) or d7 (code review only).

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix is a targeted swap: replace innerHTML/document.write/eval(string) with textContent or createElement, or wrap with DOMPurify. This is a localised pattern replacement within a component rather than a single one-line patch, since multiple sink usages may need updating, but it doesn't span cross-cutting concerns.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). DOM XSS applies to web contexts only, and the burden is scoped to the specific components that handle user-controlled data in client-side JavaScript. It doesn't shape the entire architecture, but each developer working on those UI components must be consistently vigilant about source-to-sink flows.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The canonical misconception is explicitly stated: developers who rely on server-side output encoding believe they are protected, but DOM XSS bypasses the server entirely — the payload flows from browser sources to sinks in client-side JavaScript without ever touching the server. This contradicts the mental model most developers have from learning about reflected/stored XSS, making it a serious cross-paradigm trap.

About DEBT scoring →

Also Known As

DOM-based XSS client-side XSS type-0 XSS

TL;DR

Malicious script is injected and executed via the browser DOM without any server-side involvement.

Explanation

DOM-based XSS differs from reflected and stored XSS in that the payload never reaches the server — the vulnerability exists entirely in client-side JavaScript that reads attacker-controlled data (e.g., location.hash, document.referrer) and writes it to a dangerous sink such as innerHTML, document.write(), or eval(). Because the server never sees the attack string, server-side output encoding cannot prevent it. Mitigations include using safe DOM APIs like textContent, avoiding eval-like sinks, and implementing a strict Content-Security-Policy.

How It's Exploited

An attacker crafts a URL like example.com/page#<img src=x onerror=alert(1)>. JavaScript on the page reads location.hash and assigns it to innerHTML, executing the payload in the victim's browser without ever hitting the server.

Common Misconception

Server-side output encoding prevents DOM XSS. DOM XSS never touches the server — the payload flows from a browser source (location.hash) directly to a sink (innerHTML) entirely in client-side JavaScript.

Why It Matters

DOM XSS is executed entirely in the browser without any server involvement — it bypasses server-side output encoding and is invisible to traditional WAFs and security scanners.

Common Mistakes

  • Using location.hash, document.referrer, or URL parameters as innerHTML or document.write() content without sanitisation.
  • Passing user-controlled data to eval(), setTimeout(), or setInterval() as a string argument.
  • Trusting that server-side encoding prevents DOM XSS — they operate at different layers.
  • Not using DOMPurify or equivalent for any user-supplied HTML inserted into the DOM.

Code Examples

✗ Vulnerable
// DOM XSS via URL hash:
document.getElementById('output').innerHTML = location.hash.substring(1);
// Attacker: https://example.com/page#<img src=x onerror=alert(1)>
✓ Fixed
// Safe DOM manipulation — no innerHTML with user data:
const name = getUserInput();

// Safe: textContent — never executes HTML:
document.getElementById('greeting').textContent = 'Hello, ' + name;

// Safe: createElement — escapes automatically:
const p = document.createElement('p');
p.textContent = name; // Escaped — no execution
document.body.appendChild(p);

// If HTML is needed, sanitise first:
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(userHtml); // Strips scripts

Added 15 Mar 2026
Edited 22 Mar 2026
Views 36
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 1 ping W 0 pings T 0 pings F 0 pings S 2 pings S 0 pings M 0 pings T 0 pings W 0 pings T 2 pings F 1 ping S 2 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 1 ping S 0 pings M 0 pings T 1 ping W 0 pings T 0 pings F 0 pings S
No pings yet today
No pings yesterday
Amazonbot 8 ChatGPT 5 Perplexity 4 Unknown AI 3 Google 3 Ahrefs 2 Majestic 1
crawler 24 crawler_json 1 pre-tracking 1
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Medium
⚡ Quick Fix
Never pass user-controlled data to innerHTML, document.write, eval, or setTimeout(string) — use textContent or createElement instead
📦 Applies To
javascript ES5 web
🔗 Prerequisites
🔍 Detection Hints
innerHTML = location.hash or innerHTML = location.search or innerHTML = URLSearchParams
Auto-detectable: ✓ Yes semgrep eslint
⚠ Related Problems
🤖 AI Agent
Confidence: High False Positives: Medium ✗ Manual fix Fix: Medium Context: File Tests: Update
CWE-79 CWE-83

✓ schema.org compliant