FlawOpen

flawopen.com/XSS in JavaScript/dangerouslySetInnerHTML

Is React's dangerouslySetInnerHTML safe?

Reference page
Short answer

Think of ordinary JSX as a label printer: text stays text. dangerouslySetInnerHTML switches to a markup press that interprets the supplied string as HTML. If that string contains attacker-controlled content, the browser may treat it as active markup. Use this sink only for HTML you have deliberately trusted or sanitized for the exact HTML context.

Why normal JSX doesn't need this

RISKY — the escape hatch
function Comment({ html }) {
  return <p dangerouslySetInnerHTML=
    {{ __html: html }} />;
}
SAFE — normal JSX interpolation
function Comment({ text }) {
  return <p>{text}</p>;
}

When is dangerouslySetInnerHTML appropriate?

Use it only when the product must preserve a restricted rich-text format that cannot be represented as ordinary React children. A CMS, editor, or Markdown renderer is not automatically trusted: stored content and renderer output can still contain untrusted markup. Define the allowed elements and attributes for the feature, sanitize with a maintained HTML sanitizer such as DOMPurify, and pass the sanitized result directly to this sink.

Doing it correctly

Sanitize at the rendering boundary

Sanitize immediately before assigning the value to dangerouslySetInnerHTML, using a maintained library such as DOMPurify. Do not use regular expressions as an HTML sanitizer. Avoid modifying the sanitized string afterward; a later transformation can invalidate the sanitizer's guarantees. Keep the sanitizer and its parsing environment patched.

Constrain the allowed markup

A deny-list of familiar tags is incomplete because risk can come from attributes, URL-bearing fields, namespaces, and parser behavior. Choose a small allow-list of the elements and attributes the feature needs, then configure the sanitizer for that policy. Review links and other URL-bearing attributes separately against the destinations your product permits.

How to check your codebase

grep -rn "dangerouslySetInnerHTML" --include="*.jsx" --include="*.tsx" .
For each match, trace the value from its source through every transformation. Treat user input, imported content, and stored rich text as untrusted; confirm the sanitizer policy fits the output context and that no later step changes the sanitized result.

FAQ

Is markdown rendering automatically safe?

It depends on the renderer and its configuration. Check whether raw HTML is accepted and whether the renderer sanitizes its output; do not assume Markdown conversion alone makes HTML safe.

Does this apply to Vue's v-html the same way?

Yes — same mechanism, same rule: sanitize untrusted content immediately before it reaches v-html, never trust it by default.

References

See also Cross-Site Scripting (XSS, CWE-79): How It Works and How to Prevent It in Every Language — flawopen.com Is DOMPurify Enough for Safe innerHTML? 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?