DOM-Based XSS Is Still Shipping in 2026
Cybersecurity
Server-side XSS is mostly mitigated by frameworks. DOM XSS is alive and shipping in every major SPA codebase we audit. Trusted Types is the durable fix; lint tooling and engineer training are the path to it.
By Arjun Raghavan, Security & Systems Lead, BIPI · September 28, 2024 · 7 min read
We've audited eleven React-based SPAs in 2025. Every single one shipped at least one DOM XSS. The pattern is consistent: raw HTML insertion APIs called on user-controlled content, often three levels deep in a markdown renderer or rich-text component, with sanitization that's either missing, wrong, or bypassed by a known DOMPurify edge case the team didn't track.
Server-side XSS, by contrast, was rare. Modern frameworks template-escape by default. The vulnerabilities have moved to where the framework can't help: the client-side DOM, where developers wire raw strings to the browser.
Why DOM XSS persists
The browser exposes a long list of sinks that take strings and parse them as HTML or JavaScript: innerHTML, outerHTML, eval, setTimeout/setInterval with string args, location assignment, src attributes on script and iframe, srcdoc, javascript: URLs. Any of these is a gateway from string-land to executing-code-land.
Frameworks deliberately make some sinks hard to use (React's escape-hatch HTML prop, Vue's v-html). But once a developer reaches for them (legitimate use case: rendering user-authored markdown, embedding sanitized HTML email previews), the safety wheels are off. The sanitizer becomes the sole defence, and sanitizers have bugs.
The sanitizer problem
DOMPurify is the gold standard. It still has bypasses every six to twelve months, usually involving novel HTML parsing edge cases (mXSS, namespace confusion, math/svg foreign content). The bypasses get patched within days of disclosure. The application teams using DOMPurify often don't update for months because dependency updates are scary.
We track CVE disclosures against DOMPurify and check the installed version on every audit. Three of the last eleven audits had a DOMPurify version that was vulnerable to a public PoC. Two had custom sanitization built in-house, both bypassable trivially. One had no sanitization at all on a markdown rendering path.
Trusted Types: the durable fix
Trusted Types is a browser-enforced policy that says: dangerous sinks (innerHTML, etc) only accept TrustedHTML objects, not strings. You opt in via a CSP directive: 'require-trusted-types-for script'. Once enabled, any code that passes a string to innerHTML throws a TypeError.
The point isn't to block sanitization. It's to centralise it. You define a policy that wraps DOMPurify (or your sanitizer of choice) and produces TrustedHTML objects. Every dangerous sink in your codebase has to go through that policy. Now you have one place to audit, one place to patch, and one place where the security team has a chokepoint.
- Define a Trusted Types policy in a single file using createPolicy with a sanitizing createHTML hook
- Enable enforcement via CSP: 'require-trusted-types-for script'
- Run in report-only mode first, fix violations, then enforce
- Migrate raw HTML insertion calls to use the policy
- Audit any third-party library that hits dangerous sinks (some need policy permissions)
Migration path for existing apps
Trusted Types report-only mode lets you find every violation in production traffic without breaking anything. Ship it, collect reports for two to four weeks, fix the easy violations, document the hard ones, then flip to enforce.
The hard violations are usually third-party libraries doing innerHTML internally. Some libraries (like Google Tag Manager) ship Trusted Types-compatible builds. Some don't. For the laggards, you have three options: fork and fix, replace, or grant a per-library policy that's narrower than the default.
Training engineers
DOM XSS isn't a knowledge problem at the senior level. Senior engineers know unescaped HTML insertion is dangerous. The bugs we find are introduced by mid-level engineers under time pressure who copy-paste from Stack Overflow, or who reach for the dangerous primitive because the safe one didn't fit the case.
The training that works isn't OWASP slides. It's: 'here are the three sinks in our codebase that have caused incidents, here's the lint rule that catches them, here's the policy chokepoint that contains them, here's the code review comment template you'll get if you bypass it.' Specific, applied, and enforced through tooling.
What to ship this quarter
- Run a static scan for innerHTML, outerHTML, eval, and similar HTML-string sinks across your frontend
- Audit each result: is it user-influenced, is there sanitization, is the sanitizer current
- Add the no-unsanitized ESLint rule and triage the warnings
- Define a Trusted Types policy and enable in report-only mode
- Track DOMPurify (or equivalent) versions in your dependency dashboard with auto-PR on CVE
Server-side XSS is mostly a solved problem. DOM XSS is where the bugs are now, and they will keep shipping until teams treat the client side with the same rigor they treat the server side. Trusted Types is the one durable defence. Everything else is mitigation that ages.
Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.