Escaping and XSS Protection in JSX
Text values rendered through JSX are escaped by default, which helps prevent many injection-based XSS attacks. Senior frontend engineers should also understand where this protection stops, especially with raw HTML, URL handling, DOM APIs, and third-party content.
In JSX, a value inserted as text is treated as data rather than interpreted as HTML. For example, `<div>{userInput}</div>` renders characters from `userInput` as text, so markup supplied by the user does not normally become DOM elements. React handles the necessary HTML escaping during rendering.
This protection is contextual: it applies to JSX text and attribute values rendered through React, but it is not a universal sanitizer for every kind of untrusted data. Developers still need to consider dangerous URL schemes, DOM APIs such as `innerHTML`, and APIs that intentionally interpret strings as HTML or executable code.
The main escape hatch in React is `dangerouslySetInnerHTML`. It deliberately bypasses normal JSX text escaping and should only receive HTML whose safety has been established. If untrusted HTML must be displayed, use an appropriate HTML sanitizer with a carefully chosen policy before passing it to this API.
XSS prevention also depends on the surrounding platform and application architecture. Content security policy, safe server-side handling, dependency hygiene, and avoiding unsafe DOM sinks provide additional defenses, but they do not replace correct handling of untrusted input.
A senior engineer should distinguish escaping from sanitization. Escaping changes data so it is safe for a particular output context, while sanitization examines HTML or another structured input and removes or rejects constructs that violate a safety policy.
Key points
- JSX text and attribute interpolation is escaped by default.
- Escaping is contextual and does not make every use of untrusted data safe.
- `dangerouslySetInnerHTML` bypasses normal JSX HTML escaping.
- Untrusted HTML should be sanitized before intentional HTML injection.
- XSS defense also includes safe URL handling, DOM APIs, CSP, and dependency security.
Interview questions
Why doesn't `{userInput}` in JSX normally create an XSS vulnerability?
React treats interpolated JSX values as data and escapes them when rendering them into HTML text or attributes. Therefore, HTML-looking characters supplied by a user are displayed rather than interpreted as markup in normal JSX interpolation. This is an important default protection against many reflected or stored XSS cases.
What is the security concern with `dangerouslySetInnerHTML`?
`dangerouslySetInnerHTML` is an explicit HTML injection mechanism that bypasses the normal JSX treatment of a string as text. If attacker-controlled HTML reaches it without appropriate sanitization, an attacker may be able to inject dangerous markup or script-capable content. A robust design establishes a trusted HTML boundary and sanitizes untrusted HTML before it crosses that boundary.
Is React's automatic escaping sufficient to prevent all XSS?
No. It significantly reduces risk for values rendered through normal JSX, but XSS can still arise through unsafe sinks and contexts that are outside ordinary text interpolation. Examples include raw HTML insertion, unsafe URL handling, direct DOM manipulation, and third-party code or dependencies. A production defense should therefore combine correct React usage with contextual validation or sanitization, safe DOM practices, and appropriate browser security controls.
How would you safely render user-provided rich text in a React application?
I would first define the allowed rich-text subset rather than treating arbitrary HTML as trusted. The input would be processed by a maintained HTML sanitizer configured with an allowlist appropriate to the product's requirements, and only the sanitized result would cross the HTML-rendering boundary. I would also consider URL validation, resource-loading behavior, CSP, and whether rendering HTML is necessary at all. The sanitizer configuration and dependency would be tested because a security boundary is only as strong as its policy and implementation.
How would you investigate an XSS report in a React application?
I would identify the exact source of attacker-controlled data and trace it to the DOM sink where it becomes executable or otherwise dangerous. I would determine whether the value passed through ordinary JSX escaping, a raw HTML API, an unsafe URL context, or direct DOM manipulation. Then I would reproduce the issue with a minimal payload, fix the boundary rather than merely filtering one observed payload, and add regression tests. I would also assess whether the same source reaches other sinks and whether browser-side controls such as CSP provide useful defense in depth.