Back to docs
Recipe

XSS defense layered approach

Ship a multi-layer XSS shield that catches injection at the boundary, in transit, and at render time.

Layer 1 — Input boundary

Validate and sanitize at the earliest entry point. Reject or encode anything that looks like markup before it touches your domain logic. Use an allowlist, never a blocklist.

const safe = DOMPurify.sanitize(input, { ALLOWED_TAGS: ['b','i','em','strong'], ALLOWED_ATTR: [] });

Layer 2 — Contextual encoding

Encode for the exact context where data lands — HTML body, attribute, JavaScript string, or CSS. Never mix encoding strategies.

// HTML context el.textContent = userData; // Attribute context el.setAttribute('data-name', userData);

Layer 3 — CSP headers

Deploy a strict Content-Security-Policy that blocks inline scripts and restricts script sources to trusted origins.

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; object-src 'none'; base-uri 'self';

Layer 4 — Trusted Types

Enforce Trusted Types in the browser to eliminate DOM XSS by requiring typed objects for injection sinks.

if (window.trustedTypes) { const policy = trustedTypes .createPolicy('default', { createHTML: (s) => s }); }

Defense in depth

No single layer is bulletproof. Combine input validation, contextual encoding, CSP, and Trusted Types so that a failure in one layer is caught by the next. Ship all four and sleep better.