Preventing XSS in React
React escapes what you put in JSX, so most XSS comes through the escape hatches — dangerouslySetInnerHTML, direct DOM writes, javascript URLs and third-party scripts. Defend in layers.
Cross-site scripting (XSS) is getting an attacker’s script to run in your users’ browsers, with your users’ sessions. React protects you more than older approaches did — but only on the paved road. Step off it and you’re on your own.
What React does for you
Anything you interpolate in JSX is escaped. {userInput} renders as text even if it contains <script> tags. That one
default removes the most common class of XSS bugs.
Where XSS still gets in
dangerouslySetInnerHTML. The name is the warning. If HTML is unavoidable (rendered Markdown, a CMS), sanitise it with a well-maintained library such as DOMPurify. Don’t write your own sanitiser.- Direct DOM writes.
innerHTML,insertAdjacentHTML, or appending HTML strings via refs bypass React entirely. UsetextContentif you must touch the DOM. javascript:URLs.<a href={user.website}>happily rendersjavascript:alert(1). Validate URL schemes:
const safeUrl = (u: string) => (/^https?:\/\//i.test(u) ? u : "#");
<a href={safeUrl(user.website)}>site</a>;
- Spreading user-controlled props onto elements, which can smuggle in event handlers or
hrefs. - Server-rendered state. Serialising state into a
<script>tag with plainJSON.stringifylets a</script>in the data break out. Escape<. - Third-party scripts. Every tag manager and widget runs with full access to your page.
Defence in layers
- Validate data at the boundary — everything from servers and third-party APIs (API Modelling).
- Render through JSX and avoid the escape hatches.
- Sanitise when you can’t avoid HTML.
- Content-Security-Policy. A strict CSP (
script-src 'self', nonces for inline scripts,object-src 'none') stops injected scripts running even if one slips through. This is the layer that saves you from the bug you didn’t find. HttpOnlycookies so a successful XSS can’t read session tokens.- Trusted Types, where supported, make the browser reject raw strings assigned to dangerous DOM sinks.
In practice
Every XSS finding I’ve seen in a React codebase came from an escape hatch added “just this once” — usually rendering HTML
from an API or wiring a URL from user data. A lint rule that flags dangerouslySetInnerHTML and a CSP header in the
deployment are cheap insurance. Security belongs early in the pipeline alongside tests — see Shift Left Testing — and
leaked secrets deserve the same treatment (Removing Secrets from Git History).
- #security
- #xss
- #react
- #csp
↩ Linked from (1)
- Shift Left TestingGrowing
…, and test-first habits for new features. Security shifts left too: see Removing Secrets from Git History and Preventing XSS in React. The testing layers themselves are in React Testing Strategy, and the feedback-loop vi…
↪ Links to (3)
Related by topic
- Growingcase study
A Testing Strategy Teams Actually Adopted
Frontend craft - Growingcase study
Building a Graph Component Library
Frontend craft - Growingcase study
Building a Low-Code BPMN Workflow Platform
Frontend craft - Growing
Bulletproof React
Frontend craft