DOM-Based XSS in 2026: Modern Frameworks and Trusted Types

Manish Garg
Manish Garg Associate of (ISC)² · RingSafe
Apr 25, 2026
2 min read

Last updated: April 26, 2026

DOM-based XSS happens entirely in the browser — server never sees the attack. Modern frameworks (React, Vue, Angular) reduce reflected XSS but introduce DOM-XSS via dangerouslySetInnerHTML, v-html, [innerHTML]. This article covers DOM-XSS detection patterns and the framework-specific safe patterns.

The bug

// Vulnerable React
function Profile({ bio }) {
  return <div dangerouslySetInnerHTML={{ __html: bio }} />;
}

// User-controlled bio = "<img src=x onerror=alert(document.cookie)>"
// React renders the string as HTML; XSS

Vulnerable sinks across frameworks

Framework Sink
Vanilla JS innerHTML, outerHTML, document.write, eval, setTimeout(string)
jQuery $.html(), $() with HTML string, $.parseHTML
React dangerouslySetInnerHTML
Vue v-html
Angular [innerHTML] + bypassSecurityTrust*
Svelte {@html ...}

Detection

# Static analysis with Semgrep
semgrep --config "p/javascript" --config "p/typescript" .

# Targeted regex for review
grep -rE "dangerouslySetInnerHTML|v-html|innerHTML|outerHTML|document\.write|eval\(|new Function\(" src/

# Burp Pro DOM Invader extension — interactive DOM-XSS exploration
# Right-click in Burp browser → DOM Invader

# Manual canary
# Inject <img src=x onerror=alert(1)> in every input that renders client-side
# Check if it executes in DevTools console

Source-to-sink analysis

DOM-XSS requires user-controlled source reaching dangerous sink. Sources include:

  • location.search, location.hash, location.pathname
  • document.referrer, document.cookie
  • window.name, postMessage data
  • localStorage / sessionStorage values
  • API responses (if attacker can control them)

The fix

  • Default to safe rendering — JSX {value} in React, {{ value }} in Vue automatically escape
  • Sanitise before HTML rendering — DOMPurify is the standard. DOMPurify.sanitize(userHtml)
  • CSPscript-src 'self' blocks inline script execution; bypass becomes harder
  • Trusted Types (Chrome / Edge) — require-trusted-types-for 'script' CSP directive forces all DOM sinks to receive Trusted Type objects, blocking strings entirely

Trusted Types example

// CSP header:
// Content-Security-Policy: require-trusted-types-for 'script'

// Now this throws:
element.innerHTML = "<img src=x onerror=alert(1)>";
// TypeError: This document requires 'TrustedHTML'

// Must use a policy:
const policy = trustedTypes.createPolicy('default', {
  createHTML: (str) => DOMPurify.sanitize(str)
});
element.innerHTML = policy.createHTML(userInput);
// Sanitised before assignment

The takeaway

DOM-XSS is the modern XSS variant that traditional WAFs and server-side scanners miss. Source-to-sink analysis with Semgrep + Burp DOM Invader catches systematically. Trusted Types (where browser-supported) closes the bug class architecturally. Ship Trusted Types in your next CSP iteration.

Need a real pentest?

Get a VAPT scoping call

Senior practitioner-led VAPT — not a checklist run by juniors. CVSS-scored findings, free retest, attestation letter. India's SMBs and SaaS teams.

Book VAPT scoping call Replies in 4 working hrs · India-only · Senior consultants