Skip to content
Injection Reviewed 2026-09-12

React dangerouslySetInnerHTML and untrusted HTML

React normally treats a string rendered as a child as text. dangerouslySetInnerHTML explicitly bypasses that behavior and asks the browser to interpret the string as HTML. The property is not automatically a vulnerability: the issue is whether an attacker can influence active content that reaches it.

Trust boundary: a response from an API, CMS, or external provider is data. Calling the provider “trusted” does not establish that every record is safe HTML; the content may include user submissions or compromised upstream data. React's DOM component reference documents this explicit HTML escape hatch and its risks.

Unsafe example: treating a comment as HTML

function Comment({comment}) {
  return <div dangerouslySetInnerHTML={{__html: comment}} />;
}

If the comment is user-controlled, browser interpretation can lead to script execution in the page's origin. XSS can access page data and act with the user's session. HttpOnly cookies are not directly readable by script, but do not prevent those other consequences.

Safer example: preserve the plain-text requirement

function Comment({comment}) {
  return <div>{comment}</div>;
}

The caller should validate that comment is a bounded string. React escapes the text for this HTML content position, so markup-looking text remains visible text. Do not pre-encode it into HTML entities; that can cause double encoding and changes what readers see.

When rich HTML is actually required

Define the permitted formatting and sanitize with a maintained HTML sanitizer before passing the result to the HTML sink. For a browser application using DOMPurify, a deliberately small formatting policy might be:

import DOMPurify from 'dompurify';

function RichComment({commentHtml}) {
  const clean = DOMPurify.sanitize(commentHtml, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li'],
    ALLOWED_ATTR: [],
  });
  return <div dangerouslySetInnerHTML={{__html: clean}} />;
}

This example permits no links, images, SVG, styles, or attributes. Validate input type and size before sanitizing. Do not concatenate unsanitized content or mutate the HTML after sanitization. Server-side rendering requires a supported, maintained DOM environment with the sanitizer; do not assume a browser-only setup also secures a server renderer. Check the DOMPurify project documentation for supported environments and configuration behavior.

A regex or a generic string replacement is not an HTML sanitizer. CSP and Trusted Types can add controls around injection paths, but do not make arbitrary HTML safe by themselves. Also inspect links and other attribute contexts separately from text rendering.

Regression test

Render a benign string such as <strong>Example</strong> through Comment; assert it is text, not a strong element. For RichComment, assert permitted formatting is retained while an unapproved attribute and an image element are removed. Test the browser and server-rendered paths separately if both exist. These fixtures should be local and should not execute injected code.

Related: cross-site scripting, HTML injection, and DOM HTML sinks.

Reference: CWE-79 — cross-site scripting.