How to fix Possible XSS attack when rendering HTML strings in React
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.
Quick fix
Fixes Possible XSS attack when rendering HTML strings
// 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>- Stop using dangerouslySetInnerHTML if possible
- Install and use DOMPurify for sanitization
- Create a safe HTML component with DOMPurify
- Configure DOMPurify for your use case
// BAD: XSS vulnerable<div dangerouslySetInnerHTML={{ __html: userContent }} />// GOOD: Use text content<div>{userContent}</div>// GOOD: Use markdown library with sanitizationimport ReactMarkdown from 'react-markdown';<ReactMarkdown>{userContent}</ReactMarkdown>Possible XSS attack when rendering HTML strings
What this error means
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.
Typical symptoms
- Using dangerouslySetInnerHTML with dynamic or user-supplied content
- Rendering HTML from external sources without validation
- Linters or security tools flagging dangerouslySetInnerHTML usage
- Untrusted data being set as innerHTML anywhere in the application
- User input being directly inserted into HTML content
Common causes
- Passing user-supplied HTML directly to dangerouslySetInnerHTML without sanitization
- Rendering HTML from untrusted external APIs or databases without validation
- Mistakenly trusting data that could be compromised or modified by attackers
- Misunderstanding that dangerouslySetInnerHTML bypasses React's XSS protection
- Failing to implement content security measures or sanitization libraries
- Loading HTML from user-controlled sources without proper validation
How to fix "Possible XSS attack when rendering HTML strings"
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.
Advanced notes
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.
Frequently asked questions
- What does "Possible XSS attack when rendering HTML strings" mean?
- 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.
- How do I fix "Possible XSS attack when rendering HTML strings"?
- 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.
- What causes "Possible XSS attack when rendering HTML strings"?
- Passing user-supplied HTML directly to dangerouslySetInnerHTML without sanitization Rendering HTML from untrusted external APIs or databases without validation Mistakenly trusting data that could be compromised or modified by attackers
- Is the "Possible XSS attack when rendering HTML strings" error serious?
- Yes. This is a critical-severity error that usually blocks work until it is fixed, but the steps above resolve it.
Official resources & further reading
- ๐React Official Documentation
- ๐DOMPurify - Official Website(Official Docs)
- ๐DOMPurify - GitHub Repository(Official Docs)
- ๐