FlawOpen

flawopen.com/XSS in JavaScript/DOMPurify and innerHTML

Is DOMPurify enough to make innerHTML safe?

Reference page
Short answer

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.

UNSAFE — old sanitized data reaches the sink unchanged
// Sanitized only when saved with an older policy/version.
saveToDb(DOMPurify.sanitize(input));

// Later code trusts the stored string without rechecking.
el.innerHTML = fetchedFromDb;
SAFER — sanitize for this HTML sink
// 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

Search for HTML injection sinks

rg -n -g '*.{js,ts,jsx,tsx}' '(innerHTML|outerHTML|insertAdjacentHTML|document\.write|dangerouslySetInnerHTML)' .
For each match, trace untrusted data to the sink, verify sanitization happens immediately before insertion, and check that no later code mutates the sanitized markup.

References

See also Cross-Site Scripting (XSS, CWE-79): How It Works and How to Prevent It in Every Language — flawopen.com Is Markdown Rendering Safe from XSS? Is Svelte Safe from XSS by Default? Is SVG Upload an XSS Risk? Vue v-html XSS Risk and Safe Rendering Is WKWebView loadHTMLString Safe for Untrusted Content? Reflected vs. Stored vs. DOM-Based XSS