---
# === IDENTITY ===
id: software/security/xss-prevention/2026
canonical_question: "How do I prevent Cross-Site Scripting (XSS) attacks?"
aliases:
  - "XSS prevention best practices"
  - "cross-site scripting mitigation"
  - "protect against XSS attacks"
  - "output encoding for XSS"
  - "Content Security Policy XSS"
  - "DOMPurify sanitization"
  - "CWE-79 prevention"
  - "how to stop XSS in React Angular Django"
entity_type: software_reference
domain: software > security > XSS Prevention
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-24
confidence: 0.95
version: 1.0
first_published: 2026-02-24

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Trusted Types API gaining browser support (Chrome 83+, 2020); CSP Level 3 strict-dynamic directive widely adopted (2023)"
  next_review: 2026-08-23
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "NEVER trust user input in any HTML context -- always encode or sanitize before rendering"
  - "Output encoding MUST match the insertion context (HTML body, attribute, JavaScript, URL, CSS)"
  - "NEVER use innerHTML, dangerouslySetInnerHTML, v-html, or [innerHTML] with unsanitized user data"
  - "CSP is defense-in-depth, NOT a replacement for output encoding"
  - "Blacklist/regex-based XSS filtering is insufficient -- use allowlist validation and context-aware encoding"
  - "Keep HTML sanitization libraries (DOMPurify) updated -- bypasses are discovered regularly"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need to prevent SQL injection, not XSS"
    use_instead: "software/security/sql-injection-prevention/2026"
  - condition: "Need CSRF protection (cross-site request forgery, not scripting)"
    use_instead: "software/security/csrf-prevention/2026"
  - condition: "Need general authentication/session security"
    use_instead: "software/patterns/session-management/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "framework"
    question: "What web framework or language are you using?"
    type: choice
    options: ["React", "Angular", "Vue", "Django", "Express/Node.js", "Go", "Spring", "Rails", "Other"]
  - key: "xss_context"
    question: "Where does user input appear in your output?"
    type: choice
    options: ["HTML body", "HTML attributes", "JavaScript strings", "URLs", "CSS", "Multiple contexts"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/xss-prevention/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-24)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/patterns/session-management/2026"
      label: "Session Management Patterns"
    - id: "software/patterns/jwt-implementation/2026"
      label: "JWT Implementation Guide"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/security/sql-injection-prevention/2026"
      label: "SQL Injection Prevention"
    - id: "software/security/csrf-prevention/2026"
      label: "CSRF Prevention"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Cross Site Scripting Prevention Cheat Sheet"
    author: OWASP Foundation
    url: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
    type: community_resource
    published: 2024-06-15
    reliability: authoritative
  - id: src2
    title: "How to prevent XSS"
    author: PortSwigger
    url: https://portswigger.net/web-security/cross-site-scripting/preventing
    type: technical_blog
    published: 2025-01-10
    reliability: high
  - id: src3
    title: "Content Security Policy (CSP)"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src4
    title: "CWE-79: Improper Neutralization of Input During Web Page Generation"
    author: MITRE
    url: https://cwe.mitre.org/data/definitions/79.html
    type: official_docs
    published: 2024-12-01
    reliability: authoritative
  - id: src5
    title: "DOMPurify - DOM-only XSS Sanitizer"
    author: Cure53
    url: https://github.com/cure53/DOMPurify
    type: official_docs
    published: 2025-08-01
    reliability: high
  - id: src6
    title: "DOM based XSS Prevention Cheat Sheet"
    author: OWASP Foundation
    url: https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html
    type: community_resource
    published: 2024-06-15
    reliability: authoritative
  - id: src7
    title: "Mitigate XSS with a Strict Content Security Policy"
    author: Google Web Dev
    url: https://web.dev/articles/strict-csp
    type: technical_blog
    published: 2024-03-01
    reliability: high
---

# XSS Prevention: Cross-Site Scripting Defense Guide

## TL;DR

- **Bottom line**: Context-aware output encoding combined with Content Security Policy (CSP) provides layered XSS defense -- no single technique is sufficient alone.
- **Key tool/command**: `DOMPurify.sanitize(userInput)` for client-side HTML sanitization; framework auto-escaping (React JSX, Django templates, Go html/template) for server-side.
- **Watch out for**: `dangerouslySetInnerHTML` (React), `v-html` (Vue), `[innerHTML]` (Angular), and raw `innerHTML` bypass framework auto-escaping and reintroduce XSS risk.
- **Works with**: All modern web frameworks and browsers. CSP Level 3 supported in Chrome 59+, Firefox 58+, Safari 15.4+, Edge 79+.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER trust user input in any HTML context -- always encode or sanitize before rendering
- Output encoding MUST match the insertion context (HTML body, attribute, JavaScript, URL, CSS)
- NEVER use innerHTML, dangerouslySetInnerHTML, v-html, or [innerHTML] with unsanitized user data
- CSP is defense-in-depth, NOT a replacement for output encoding
- Blacklist/regex-based XSS filtering is insufficient -- use allowlist validation and context-aware encoding
- Keep HTML sanitization libraries (DOMPurify) updated -- bypasses are discovered regularly

## Quick Reference

| # | XSS Type | Vector | Impact | Prevention |
|---|---|---|---|---|
| 1 | Reflected XSS | Malicious URL parameter reflected in page response | Session hijacking, phishing | HTML-encode all reflected output; validate input on arrival |
| 2 | Stored XSS | Malicious payload saved to DB and rendered to other users | Account takeover, worm propagation | Encode on output; sanitize HTML if rich text needed |
| 3 | DOM-based XSS | Client-side JS reads attacker-controlled source (URL, postMessage) and passes to dangerous sink (innerHTML, eval) | Full client-side compromise | Avoid dangerous sinks; use textContent, use DOMPurify |
| 4 | Mutation XSS (mXSS) | Browser HTML parser mutates sanitized markup into executable form | Bypasses sanitizers | Keep DOMPurify updated; use Trusted Types API |
| 5 | Blind XSS | Stored payload executes in admin panel or internal tool | Internal system compromise | Encode all output in admin views; monitor with XSS Hunter |

**Output Encoding by Context:**

| # | Output Context | Encoding Rule | Safe Function | Dangerous Pattern |
|---|---|---|---|---|
| 1 | HTML body | HTML entity encode `& < > " '` | `textContent`, framework auto-escape | `innerHTML = userInput` |
| 2 | HTML attribute | HTML entity encode; always quote attributes | `setAttribute(name, val)` | `<div title=` + userInput + `>` |
| 3 | JavaScript string | Unicode-escape `\uXXXX` or `\xHH` format | JSON.stringify() into script context | `var x = '` + userInput + `'` |
| 4 | URL parameter | Percent-encode `%HH` | `encodeURIComponent()` | `href="` + userInput + `"` |
| 5 | URL scheme | Allowlist http/https only | Validate protocol before use | `href=` + userInput (allows `javascript:`) |
| 6 | CSS value | CSS hex encode `\XX`; allowlist values | CSS custom properties with validated input | `style="color:` + userInput + `"` |
| 7 | JSON in HTML | JSON.stringify + HTML entity encode | `<script>var x = {{data\|tojson}}</script>` (Jinja2) | `<script>var x = ${userInput}</script>` |

## Decision Tree

```
START: Where does user input appear in your output?
├── HTML body text?
│   ├── YES → Use framework auto-escaping (React JSX, Django {{ }}, Go html/template)
│   └── NO ↓
├── HTML attribute value?
│   ├── YES → HTML-encode value AND always quote the attribute
│   └── NO ↓
├── Inside <script> tag or JavaScript string?
│   ├── YES → Use JSON.stringify() + HTML entity encode, or pass via data attributes
│   └── NO ↓
├── Inside a URL (href, src)?
│   ├── YES → Validate protocol is http/https, then encodeURIComponent() for params
│   └── NO ↓
├── Inside CSS (style attribute)?
│   ├── YES → Allowlist valid CSS values; never interpolate user input directly
│   └── NO ↓
├── User needs to submit rich HTML content?
│   ├── YES → Sanitize with DOMPurify server-side AND client-side before rendering
│   └── NO ↓
└── DEFAULT → HTML-encode all output + deploy strict CSP as defense-in-depth
```

## Step-by-Step Guide

### 1. Deploy a strict Content Security Policy

CSP prevents inline script execution even if an XSS payload bypasses encoding. Use nonce-based CSP for the strongest protection. [src3]

```http
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{random}' 'strict-dynamic';
  style-src 'self' 'nonce-{random}';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none';
```

Start with report-only mode to avoid breaking existing functionality:

```http
Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'nonce-{random}' 'strict-dynamic';
  report-uri /csp-report;
```

**Verify**: Open browser DevTools > Console -- CSP violations appear as errors. Check `report-uri` endpoint for reports.

### 2. Implement server-side output encoding

Use your framework's built-in auto-escaping. Every major framework escapes by default when used correctly. [src1]

**React** -- JSX auto-escapes by default:
```jsx
// SAFE: React auto-escapes this
function UserGreeting({ name }) {
  return <h1>Hello, {name}</h1>;
}
```

**Django** -- template auto-escaping:
```html
<!-- SAFE: Django auto-escapes {{ }} by default -->
<p>Welcome, {{ user.display_name }}</p>
```

**Go** -- html/template auto-escapes:
```go
// SAFE: html/template context-aware auto-escaping
tmpl := template.Must(template.New("page").Parse(
    `<p>Hello, {{.Name}}</p>`))
tmpl.Execute(w, data)
```

**Verify**: Inject `<script>alert(1)</script>` as user input -- it should render as visible text, not execute.

### 3. Sanitize user-supplied HTML (when rich text is required)

When you must accept HTML from users (comments, WYSIWYG editors), sanitize with DOMPurify. [src5]

```javascript
import DOMPurify from 'dompurify';  // v3.x

// Sanitize on the server (Node.js with jsdom):
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = require('dompurify')(window);

const cleanHTML = DOMPurify.sanitize(userInput, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li'],
  ALLOWED_ATTR: ['href', 'title'],
  ALLOW_DATA_ATTR: false
});
```

**Verify**: Test with payload `<img src=x onerror=alert(1)>` -- should be stripped to empty string or `<img src="x">` with no handler.

### 4. Validate and encode URL schemes

Prevent `javascript:` and `data:` URL injection in href and src attributes. [src2]

```javascript
function isSafeUrl(url) {
  try {
    const parsed = new URL(url, window.location.origin);
    return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
  } catch {
    return false;
  }
}

// Use before setting href
const userUrl = getUserInput();
if (isSafeUrl(userUrl)) {
  link.setAttribute('href', userUrl);
}
```

**Verify**: Test with `javascript:alert(1)` -- should be rejected.

### 5. Enable Trusted Types (modern browsers)

Trusted Types prevent DOM XSS by requiring typed objects for dangerous sinks. [src7]

```http
Content-Security-Policy: require-trusted-types-for 'script'
```

```javascript
// Create a policy for your app
if (window.trustedTypes) {
  const policy = trustedTypes.createPolicy('default', {
    createHTML: (input) => DOMPurify.sanitize(input),
    createScriptURL: (input) => {
      const url = new URL(input, location.origin);
      if (url.origin === location.origin) return url.toString();
      throw new TypeError('Untrusted URL');
    }
  });
}
```

**Verify**: Setting `innerHTML` with a raw string will throw a TypeError in the browser console when Trusted Types is enforced.

## Code Examples

### React/JSX: Safe Dynamic Content Rendering

```jsx
import DOMPurify from 'dompurify';  // ^3.0.0

// SAFE: React auto-escapes expressions in JSX
function Comment({ author, text }) {
  return (
    <div className="comment">
      <strong>{author}</strong>  {/* auto-escaped */}
      <p>{text}</p>              {/* auto-escaped */}
    </div>
  );
}

// SAFE: When rich HTML is required, sanitize first
function RichComment({ htmlContent }) {
  const clean = DOMPurify.sanitize(htmlContent, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
    ALLOWED_ATTR: ['href']
  });
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

// SAFE: URL validation for user-provided links
function UserLink({ url, label }) {
  const safeUrl = /^https?:\/\//i.test(url) ? url : '#';
  return <a href={safeUrl}>{label}</a>;
}
```

### Node.js/Express: Helmet CSP + Output Encoding

> Full script: [node-js-express-helmet-csp-output-encoding.js](scripts/node-js-express-helmet-csp-output-encoding.js) (31 lines)

```javascript
const express = require('express');     // ^4.18.0
const helmet = require('helmet');       // ^7.1.0
const crypto = require('crypto');
const app = express();
// Generate per-request nonce for CSP
# ... (see full script)
```

### Python/Django: Template Auto-Escaping

```python
# Django auto-escapes all template variables by default
# settings.py -- no extra config needed for auto-escaping

# views.py
from django.shortcuts import render
from django.utils.html import escape  # manual encoding when needed

def profile(request, username):
    # Django templates auto-escape {{ username }}
    return render(request, 'profile.html', {'username': username})

# For manual encoding outside templates:
safe_value = escape(user_input)  # HTML entity encodes & < > " '

# DANGEROUS: |safe filter disables auto-escaping
# {{ user_input|safe }}  <-- NEVER use with untrusted data

# For JSON in templates, use |json_script filter (Django 2.1+):
# <script id="user-data" type="application/json">{{ data|json_script:"user-data" }}</script>
# This properly escapes </script> and other dangerous sequences
```

### Go: html/template Context-Aware Escaping

> Full script: [go-html-template-context-aware-escaping.go](scripts/go-html-template-context-aware-escaping.go) (32 lines)

```go
package main
import (
    "html/template"  // NOT text/template -- html/template auto-escapes
    "net/http"
)
# ... (see full script)
```

## Anti-Patterns

### Wrong: Blacklist-based XSS filtering

```javascript
// BAD -- blacklist filtering is trivially bypassed
function sanitize(input) {
  return input
    .replace(/<script>/gi, '')
    .replace(/onerror/gi, '')
    .replace(/javascript:/gi, '');
}
// Bypass: <scr<script>ipt>, <img src=x OnError=alert(1)>, java&#x73;cript:
```

### Correct: Context-aware output encoding

```javascript
// GOOD -- encode based on output context
function escapeHtml(str) {
  const map = { '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#x27;' };
  return String(str).replace(/[&<>"']/g, c => map[c]);
}
// Or use framework auto-escaping (React JSX, template engines)
```

### Wrong: Encoding in the wrong context

```html
<!-- BAD -- HTML encoding inside a JavaScript context does NOT prevent XSS -->
<script>
  var name = '{{html_escaped_user_input}}';
  // If input is: '; alert(1); // -- HTML encoding won't escape the quotes
</script>
```

### Correct: JavaScript context encoding

```html
<!-- GOOD -- use JavaScript-specific encoding or data attributes -->
<div id="user" data-name="{{html_escaped_user_input}}"></div>
<script>
  var name = document.getElementById('user').dataset.name;
  // Data attribute is HTML-decoded by the browser, value stays a string
</script>
```

### Wrong: CSP with unsafe-inline

```http
# BAD -- unsafe-inline defeats the purpose of CSP for XSS
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
```

### Correct: Nonce-based CSP

```http
# GOOD -- nonce-based CSP blocks injected inline scripts
Content-Security-Policy: default-src 'self'; script-src 'nonce-abc123' 'strict-dynamic'
```

### Wrong: innerHTML with user data

```javascript
// BAD -- direct DOM injection of user input
document.getElementById('output').innerHTML = userInput;
// Even "harmless" HTML can contain event handlers: <img src=x onerror=alert(1)>
```

### Correct: textContent or DOMPurify

```javascript
// GOOD -- textContent for plain text (no HTML needed)
document.getElementById('output').textContent = userInput;

// GOOD -- DOMPurify for when HTML rendering is required
import DOMPurify from 'dompurify';
document.getElementById('output').innerHTML = DOMPurify.sanitize(userInput);
```

## Common Pitfalls

- **JSON data in script tags**: Inserting JSON into `<script>` blocks without escaping allows `</script>` injection. Fix: Use `JSON.stringify()` and replace `</` with `<\/`, or use Django's `|json_script` filter. [src1]
- **URL scheme injection**: `<a href="{{user_url}}">` allows `javascript:alert(1)` even with HTML encoding. Fix: Validate that URL protocol is `http:` or `https:` before use. [src2]
- **SVG XSS**: SVGs can contain `<script>` tags and event handlers. Uploading SVG files or inlining user SVG content enables XSS. Fix: Sanitize SVGs with DOMPurify (supports SVG mode), or serve user SVGs with `Content-Type: image/svg+xml` and CSP. [src5]
- **Template literal injection (JS)**: Using template literals with user input (`eval(\`Hello ${name}\`)`) enables code execution. Fix: Never use `eval()`, `new Function()`, or `setTimeout(string)` with user data. [src6]
- **Markdown rendering XSS**: Markdown parsers that output raw HTML can introduce XSS via `<script>`, `<img onerror>`, or `[link](javascript:alert(1))`. Fix: Use a sanitizing Markdown renderer or pipe output through DOMPurify. [src1]
- **postMessage without origin check**: DOM XSS via `window.addEventListener('message', handler)` without verifying `event.origin`. Fix: Always check `event.origin` against an allowlist. [src6]
- **React dangerouslySetInnerHTML from API**: Backend returns HTML that frontend renders with `dangerouslySetInnerHTML` without sanitization. Fix: Sanitize with DOMPurify even for "trusted" backend data. [src5]
- **Double encoding mistakes**: Encoding output twice (server-side + client-side) shows `&amp;lt;` instead of `<` to users. Fix: Encode exactly once, at the point of output. [src2]

## Diagnostic Commands

```bash
# Test CSP in report-only mode (no enforcement, logs violations)
# Add this header to your server:
# Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

# Scan for XSS vulnerabilities with OWASP ZAP (Docker)
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
  -t https://your-site.com -r report.html

# Check current CSP headers of a site
curl -sI https://your-site.com | grep -i content-security-policy

# Validate CSP policy with Google's CSP Evaluator
# Visit: https://csp-evaluator.withgoogle.com/

# Browser DevTools: check for CSP violations
# Chrome: DevTools > Console > filter for "[Report Only]" or CSP errors
# Firefox: DevTools > Console > filter for "Content-Security-Policy"

# Find dangerous sinks in JavaScript codebase
grep -rn 'innerHTML\|outerHTML\|document\.write\|eval(' --include="*.js" --include="*.ts" .

# Find React dangerouslySetInnerHTML usage
grep -rn 'dangerouslySetInnerHTML' --include="*.jsx" --include="*.tsx" .

# Find Vue v-html usage
grep -rn 'v-html' --include="*.vue" .
```

## Version History & Compatibility

| Standard/Tool | Version | Status | Key Feature |
|---|---|---|---|
| CSP Level 3 | W3C CR | Current | `strict-dynamic`, nonce-based policies |
| CSP Level 2 | W3C Rec | Supported | `script-src`, `style-src`, `report-uri` |
| Trusted Types | Draft | Chrome 83+, Firefox behind flag | DOM sink protection |
| DOMPurify | 3.x | Current | Trusted Types support, ES module |
| DOMPurify | 2.x | Maintained | CommonJS, UMD bundles |
| Sanitizer API | Draft | Chrome 105+ (origin trial) | Native browser HTML sanitization |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Any web app renders user input in HTML | Static site with zero user input | No XSS prevention needed |
| Building a WYSIWYG editor or comment system with rich text | You can use plain text only (no HTML rendering) | `textContent` + framework auto-escaping only |
| CSP is your only server-level control | You assume WAF blocks all XSS | Output encoding at the application level |
| Third-party content is embedded (ads, widgets) | Content is from fully trusted internal sources only | Minimal CSP + auto-escaping may suffice |

## Important Caveats

- Framework auto-escaping only protects the contexts the framework manages -- manual DOM manipulation, `eval()`, and dynamic script creation are not covered
- CSP `strict-dynamic` trusts any script loaded by an already-trusted script, so a trusted script with a gadget (e.g., Angular, jQuery) can be exploited
- DOMPurify and all client-side sanitizers can be bypassed if the attacker controls the page before the sanitizer runs -- server-side sanitization is the primary defense
- The Sanitizer API (browser-native) is still in draft and not production-ready as of 2026
- SVG and MathML contexts have different parsing rules than HTML -- test sanitization specifically for these content types
- HTTP-only cookies prevent session theft via XSS but do not prevent other XSS impacts (defacement, keylogging, phishing)

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Session Management Patterns](/software/patterns/session-management/2026)
- [JWT Implementation Guide](/software/patterns/jwt-implementation/2026)
- [SQL Injection Prevention](/software/security/sql-injection-prevention/2026)
- [CSRF Prevention](/software/security/csrf-prevention/2026)
