dangerouslySetInnerHTML bypasses React's default XSS protection — anything passed through it renders as live HTML, including <script> tags, event handlers, and <iframe> exploits. When the HTML source traces back to user input, fetched data, search params, or a markdown parser that permits raw HTML, an attacker who controls that input controls script execution in every viewer's browser: session theft, token exfiltration, UI takeover, all in-scope. AI coding tools reach for dangerouslySetInnerHTML whenever a task mentions "render markdown" or "show rich text" — and because React flagged the API with a scary name but not a sanitizer, tools treat the name as the safety measure and forget DOMPurify entirely. The pattern is the single most common XSS vector in React codebases.
High because the attack is stored-XSS-grade when the input is user-controlled, but dependent on an attacker actually reaching that field with hostile input — not an automatic compromise like a leaked key.
Pipe the HTML through a sanitizer before rendering:
import DOMPurify from 'isomorphic-dompurify'
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.body) }} />
Deeper remediation guidance and cross-reference coverage for this check lives in the security-hardening Pro audit — run that after applying this fix for a more exhaustive pass on the same topic.
project-snapshot.injection.no-dangerous-html-from-inputhighdangerouslySetInnerHTML={{ __html: X }} in JSX/TSX files. For each, trace the source of X. Classify each as: (a) static literal/template with no interpolation, (b) constant from a trusted source (e.g., a sanitized markdown render via DOMPurify or sanitize-html), or (c) directly from props, state, fetch, or searchParams. Count each category.v-html; Svelte uses {@html} — those are different patterns). Note the framework when skipping.dangerouslySetInnerHTML without an explicit sanitizer step — many markdown parsers allow raw HTML through by default."Found N dangerouslySetInnerHTML usages; all N use static or sanitized content.""3 usages of dangerouslySetInnerHTML with untrusted input: app/posts/[id]/page.tsx renders post.body directly".api-security or security-hardening audit.import DOMPurify from 'isomorphic-dompurify'
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.body) }} />