Using dangerouslySetInnerHTML to render untrusted HTML content can expose your React app to cross-site scripting (XSS) attacks. Attackers can inject malicious JavaScript through user-supplied or untrusted data.
This warning alerts you to a critical security vulnerability in your React application. By default, React escapes all values embedded in JSX to prevent XSS attacks. However, the dangerouslySetInnerHTML property explicitly bypasses React's built-in protection. When you use it to render user-supplied data or untrusted content without sanitization, attackers can inject malicious JavaScript code that executes in your users' browsers. This compromises the security of your entire application and can lead to data theft, session hijacking, or unauthorized actions performed on behalf of your users.
The safest approach is to avoid dangerouslySetInnerHTML entirely. If you need to render formatted content, consider these alternatives:
// BAD: XSS vulnerable
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// GOOD: Use text content
<div>{userContent}</div>
// GOOD: Use markdown library with sanitization
import ReactMarkdown from 'react-markdown';
<ReactMarkdown>{userContent}</ReactMarkdown>React automatically escapes text content, so this is much safer.
If you must use dangerouslySetInnerHTML, always sanitize the HTML first using DOMPurify:
npm install dompurify
npm install --save-dev @types/dompurifyDOMPurify is a widely trusted library endorsed by OWASP for HTML sanitization. It removes dangerous elements and attributes while preserving safe HTML.
Wrap sanitization in a reusable React component:
import DOMPurify from 'dompurify';
import React from 'react';
interface SafeHTMLProps {
content: string;
className?: string;
}
export const SafeHTML: React.FC<SafeHTMLProps> = ({ content, className }) => {
const sanitized = DOMPurify.sanitize(content);
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: sanitized }}
/>
);
};Now all developers can safely render HTML:
<SafeHTML content={userProvidedHTML} />DOMPurify allows you to customize what tags and attributes are allowed:
import DOMPurify from 'dompurify';
const sanitizeHTML = (html: string) => {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'br', 'p'],
ALLOWED_ATTR: ['href', 'title'],
KEEP_CONTENT: true,
});
};
export const SafeHTML: React.FC<SafeHTMLProps> = ({ content, className }) => {
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: sanitizeHTML(content) }}
/>
);
};Adjust ALLOWED_TAGS and ALLOWED_ATTR based on your specific needs.
Always assume that data from APIs, databases, user input, or URLs could be malicious. Apply sanitization consistently:
// BAD: Trusting API response
const [html, setHtml] = React.useState('');
React.useEffect(() => {
fetch('/api/content').then(r => r.json()).then(data => {
setHtml(data.htmlContent); // VULNERABLE!
});
}, []);
return <div dangerouslySetInnerHTML={{ __html: html }} />;
// GOOD: Sanitize API response
const [html, setHtml] = React.useState('');
React.useEffect(() => {
fetch('/api/content').then(r => r.json()).then(data => {
setHtml(DOMPurify.sanitize(data.htmlContent));
});
}, []);
return <div dangerouslySetInnerHTML={{ __html: html }} />;Add a Content Security Policy to your server configuration as a defense-in-depth measure:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';For Next.js, add to next.config.js:
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self'"
}
];
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
}
];
}
};CSP acts as a safety net that prevents injected scripts from executing even if sanitization is bypassed.
React's default JSX escaping is one of its greatest security features. Before using dangerouslySetInnerHTML, exhaustively explore alternatives: use JSX for structured content, markdown libraries with built-in sanitization, or plain text rendering.
DOMPurify uses a whitelist-based approach, which is more secure than blacklist-based sanitizers. It's actively maintained, endorsed by OWASP, and used in production by major companies.
If rendering user-generated HTML, consider server-side sanitization in addition to client-side sanitization. Never rely on client-side validation alone. Always apply the principle of defense in depth with multiple layers of protection.
For rich text editing, consider using dedicated libraries like Slate, Draft.js, or ProseMirror that handle security internally, rather than accepting arbitrary HTML.
Prop spreading could cause security issues
Prop spreading could cause security issues
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
React.FC expects children prop to be defined
React.FC no longer includes implicit children prop