React was designed with XSS prevention in mind, but is it completely safe from XSS attacks? Learn how your React applications are protected against cross-site scripting, when they can still be vulnerable, and what best practices (and testing methods) will help to keep your application environments secure.
React is designed to help developers build secure and dynamic web applications. However, like any front-end framework, it can still be vulnerable to cross-site scripting (XSS) if used incorrectly. Understanding React’s built-in protections and the scenarios in which they can fail is essential for writing secure components.
XSS is a security vulnerability that allows attackers to inject malicious scripts into web pages viewed by other users. These scripts run in the victim’s browser and can steal sensitive data, impersonate users, or manipulate application behavior. It remains one of the most common vulnerabilities in web applications, frequently appearing in the OWASP Top 10.
Modern front-end frameworks rely heavily on client-side rendering, dynamic content, and user interactions, increasing the likelihood of developers inadvertently introducing unsafe handling of user input. Without proper validation and sanitization, user-controlled data can be executed in the browser context, leading to XSS.
The React framework (initially React.js) was one of the first to be built with security in mind and includes features that make it harder to introduce XSS vulnerabilities. One of the core innovations in React is JSX, a syntax extension that allows developers to write HTML-like elements directly within JavaScript. JSX is compiled to JavaScript and rendered using React’s virtual DOM, which adds a layer of abstraction and safety when interacting with the DOM.
React helps protect against XSS primarily by escaping values rendered in JSX before inserting them into the DOM.
React escapes all values embedded in JSX expressions by default before rendering them to the DOM. This is done using a mechanism that ensures injected data is treated as plain text, not interpreted as executable HTML or JavaScript. It significantly reduces the likelihood of XSS through normal data binding.
Characters like <
, >
, &
, '
, and "
are escaped to their respective HTML entities (e.g., <
, >
). This prevents them from being parsed as HTML tags or attributes, which could otherwise enable code injection.
React’s virtual DOM adds another layer of protection by controlling how updates are applied to the real DOM. Instead of directly manipulating the DOM based on strings or templates, React builds a virtual representation that is diffed and sanitized before updates are made. This minimizes the risk of unexpected script execution.
Dynamic content from user input or APIs can be safely displayed using JSX, as React escapes it by default. However, if developers bypass these mechanisms—such as by injecting HTML manually—XSS risks reappear. Safe rendering requires avoiding raw HTML and relying on trusted sanitization libraries when necessary.
Despite its protective defaults, React is not immune to XSS vulnerabilities. Developers can still introduce risk through specific APIs or by misusing features that bypass the normal safety mechanisms. JSX itself is safe when used properly, but it is not a sandbox—it will render whatever it’s instructed to, which still makes secure coding practices essential.
dangerouslySetInnerHTML
One of the most common ways XSS can re-enter a React app is through the use of dangerouslySetInnerHTML
, which bypasses React’s built-in escaping.
The dangerouslySetInnerHTML
attribute exists to support scenarios where raw HTML needs to be rendered, such as displaying content from a CMS or rendering HTML-formatted Markdown. Its usage is explicitly marked as “dangerous” to discourage misuse and highlight the associated security risks.
If developers use dangerouslySetInnerHTML
to render content that includes user input or untrusted data, attackers can inject scripts that will be executed in the user’s browser. This bypasses React’s escaping mechanisms entirely and creates a direct path for XSS.
eval()
, innerHTML
, or document.write()
Functions like eval()
or properties like innerHTML
are inherently dangerous because they can execute arbitrary JavaScript. Using them on untrusted data creates XSS vulnerabilities, as any malicious script passed in will run with full privileges in the page context.
Loading external scripts into a React application without integrity checks or sandboxing introduces risks. These scripts could be compromised or behave maliciously, potentially accessing sensitive data or manipulating the app’s state.
Client-side routing libraries often use dynamic segments from URLs to render content. If those segments are injected directly into the DOM without escaping or sanitization, attackers can exploit them to execute scripts via DOM-based XSS.
Consider a component that renders Markdown using a parser that outputs raw HTML. If this output is injected with dangerouslySetInnerHTML
without sanitization, attackers can exploit it.
function MarkdownViewer({ content }) { Â return ( Â Â <div dangerouslySetInnerHTML={{ __html: marked(content) }} /> Â );}// Malicious input: `')"`
Without sanitizing content
, this approach can allow injection of <img>
tags with onerror
handlers that execute JavaScript.
Suppose an app fetches a news summary from an external API and renders it with raw HTML.
useEffect(() => { Â fetch('/api/news') Â Â .then(res => res.text()) Â Â .then(html => setContent(html));}, []);return <div dangerouslySetInnerHTML={{ __html: content }} />;
If the API is compromised or the data is not properly filtered, attackers can inject scripts through the returned HTML, exposing users to XSS.
dangerouslySetInnerHTML
Even with good intentions, misconfigured or misunderstood uses of dangerouslySetInnerHTML
can introduce XSS.
const SafeHtml = ({ rawHtml }) => ( Â <div dangerouslySetInnerHTML={{ __html: rawHtml }} />);// rawHtml might contain: `<script>alert('XSS')</script>`
Unless rawHtml
is sanitized before being passed in, this will execute arbitrary scripts embedded in the input.
dangerouslySetInnerHTML
unless absolutely necessaryAvoid it whenever possible. When raw HTML must be rendered, sanitize the content using a robust library and validate the source of the data.
Libraries like DOMPurify are specifically designed to clean HTML and remove potentially dangerous elements and attributes. Always use them before injecting HTML into the DOM.
Server-side validation ensures that malicious data does not enter your system, complementing client-side protections. It also prevents tampered requests or API responses from introducing harmful content.
React’s design discourages inline handlers for good reason. Stick to JSX event binding and avoid constructing handlers from user input, which could result in code injection.
A strong CSP can block script execution from unauthorized sources and add a critical layer of defense. It’s especially useful as a fallback when other protections fail.
As with any other web application, your React-based app should use best-practice security headers to minimize the risk not only of XSS but of many other classes of attacks.
Add CSP headers to your HTTP responses to control script execution. For example, setting Content-Security-Policy: default-src 'self'
restricts scripts to those hosted on your own domain.
Mark authentication cookies as HttpOnly
to prevent access from JavaScript and as Secure to ensure they are transmitted only over HTTPS. This protects session data even if other parts of the app are compromised.
Set Referrer-Policy
to limit sensitive referrer data in requests. While X-XSS-Protection
is deprecated, it may still offer limited protection in older browsers.
Linting tools and static analyzers can identify common coding issues, including unsafe API usage. However, they lack execution context and often miss runtime vulnerabilities.
Security professionals can simulate attack scenarios to test how the app handles malicious input. This includes checking for DOM manipulation flaws and unsafe data handling patterns.
Dynamic application security testing (DAST) tools like Invicti interact with live applications to identify real, exploitable XSS vulnerabilities. With capabilities like proof-based scanning, these tools provide verified results that help teams focus on fixing true security issues rather than chasing false positives.