flawopen.com/XSS in JavaScript/DOMPurify and innerHTML
Is DOMPurify enough to make innerHTML safe?
DOMPurify is a strong allow-list sanitizer for rich HTML when you pass its output directly to an HTML sink without changing it afterward. Think of it like screening a package at the loading dock: if another process repacks it or sends it through a different entrance, the original inspection no longer covers what arrives. For plain text, use textContent and avoid HTML parsing entirely.
// Sanitized only when saved with an older policy/version. saveToDb(DOMPurify.sanitize(input)); // Later code trusts the stored string without rechecking. el.innerHTML = fetchedFromDb;
// If rich HTML is required, sanitize with the current library // immediately before inserting into the HTML context. el.innerHTML = DOMPurify.sanitize(untrustedHtml, { USE_PROFILES: { html: true }, }); // If the content is plain text, do not parse it as HTML. textNode.textContent = untrustedText;
Why sanitizing once at input is a weaker pattern
Sanitizing at save time is not automatically unsafe, but it creates a stale-data boundary: a later renderer may trust older output after a sanitizer fix, a policy change, or a path that skipped sanitization. Re-sanitize at the rendering boundary with the current library and the policy for the exact sink. Do not modify or pass the cleaned markup through another library before insertion; post-processing can invalidate the sanitizer’s result.
Configuration and output context matter
DOMPurify’s defaults allow HTML, SVG, and MathML. If the feature needs only HTML, narrow the profile with USE_PROFILES: { html: true }. Avoid widening tags or attributes without review. DOMPurify is not a CSS sanitizer and does not prevent all external resource requests; sanitize for HTML only, never reuse the result in JavaScript, an attribute, XML, SVG, or CSS context.
Trusted Types and server-side rendering
Where supported, enforce Trusted Types with a Content Security Policy so string assignments to HTML injection sinks are rejected unless they pass through an approved policy. DOMPurify can produce TrustedHTML; this reduces accidental sink use but does not repair unsafe sanitizer configuration or post-processing. On the server, DOMPurify relies on a DOM implementation: keep jsdom current and do not pair it with happy-dom for security-sensitive sanitization.
Production checklist
- ✓Use textContent for plain text. Use DOMPurify only when the product must render a constrained rich-HTML subset.
- ✓Sanitize at the final HTML sink with the current DOMPurify release and a reviewed, minimal allow-list; never mutate the returned markup afterward.
- ✓Keep HTML output in an HTML context. Do not reuse it inside JavaScript, CSS, an attribute, XML, SVG, or another parser context.
- ✓Review persistent setConfig(), hooks, ADD_TAGS/ADD_ATTR, server-side DOM versions, and Trusted Types policy/CSP together.
Search for HTML injection sinks
rg -n -g '*.{js,ts,jsx,tsx}' '(innerHTML|outerHTML|insertAdjacentHTML|document\.write|dangerouslySetInnerHTML)' .References
- DOMPurify — README and configuration guidance
- DOMPurify — Security Goals and Threat Model
- OWASP — DOM-based XSS Prevention Cheat Sheet
- OWASP — JavaScript and TypeScript Security Cheat Sheet
- FlawOpen — XSS in JavaScript