Login
ChallengesLearn
Scoreboard
Teams
SPNZ

LearnCross Site ScriptingXSS Defense
Cross Site Scripting·Lesson 5 of 12

XSS Defense

CSP, context-aware escaping, sanitisation libraries, and the review checklist that catches what scanners miss. How to ship HTML that cannot execute attacker code.

Intermediate14 min
XSSDefenseCSP
Loading lesson…
PreviousDOM-based XSSNextXSS Payload Techniques

© 2026 SPNZ.

Terms of ServicePrivacy PolicyCookie Policy

Defending against XSS requires multiple layers. No single technique — not escaping, not validation, not CSP — is sufficient on its own. The industry standard is defence in depth: apply context-aware output encoding at every injection point, enforce a strict Content Security Policy as a safety net, and use sanitisation libraries where HTML is intentionally allowed.

What you'll be able to do
  • Describe the three layers of XSS defence: escaping, CSP, sanitisation.
  • Construct a CSP header that blocks inline scripts and eval.
  • Use DOMPurify to sanitise rich HTML input safely.
  • Explain how CSP would have mitigated the GitHub 2016 stored XSS.
Key terms
Content Security Policy
An HTTP response header that restricts which resources the browser can load and execute. A strict CSP blocks inline scripts, eval(), and connections to unauthorised origins.
Context-aware escaping
Applying the correct encoding scheme based on where the value is rendered — HTML entity encoding for body context, attribute encoding for attributes, JavaScript encoding for script strings.
Trusted Types
A browser API that enforces sanitisation at the DOM sink level. Developers must specify that a value is safe before passing it to innerHTML, preventing accidental XSS.
Nonce
A cryptographically random value generated per request and included in both the CSP header and the script tag. The browser executes only scripts whose nonce matches.
What is it?

Defence in depth for the browser

The primary defence against XSS is context-aware output encoding. Every value that originates from user input must be escaped according to the HTML context it lands in — HTML body, HTML attribute, JavaScript string, CSS, or URL. Server-side template engines such as React JSX, Jinja2, and Handlebars apply HTML body escaping by default, but developers can inadvertently bypass it with APIs like dangerouslySetInnerHTML in React or the | safe filter in Jinja2.

Content Security Policy provides the second layer. Even if escaping fails, a strict CSP can prevent the injected script from executing. The policy is delivered via the Content-Security-Policy HTTP response header and instructs the browser about which scripts, styles, and connections are legitimate. CSP is not a silver bullet — it requires careful tuning and can break existing functionality — but it is the most effective defence-in-depth measure available.

Three layers of XSS defence
Mini Map
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Try it

CSP configurator

Toggle CSP directives and try to execute a script. The dashboard shows whether the policy allows or blocks each script. Experiment with strict policies and see how nonce-based allowlisting lets legitimate scripts run while blocking inline event handlers.

csp-dashboardstaging
policy editorSsecadmin
csp-policy · directives

CSP directives

Toggle directives on and off, then test what happens when a script tries to execute.

response header
Content-Security-Policy: default-src 'self'
Violation dashboardno tests run

Toggle directives and run a test to see CSP violations.

The dashboard simulates how CSP blocks or allows each script attempt.

Real-world relevance

GitHub 2016: CSP saves the day

In 2016, security researcher Frederik Braun discovered a stored XSS vulnerability in GitHub's comment system. A crafted comment could inject arbitrary HTML into the page. However, GitHub had deployed a strict Content Security Policy months earlier that blocked inline scripts. When the vulnerability was disclosed, the CSP prevented any script execution, reducing the severity from critical to informational. GitHub was able to fix the escaping issue without any user impact.

This case is the strongest real-world argument for CSP as defence in depth. The escaping failure was real — a stored XSS was present in production — but the CSP neutralised it completely. Without CSP, an attacker could have stolen session cookies, accessed private repositories, and performed actions on behalf of every user who viewed the infected comment page.

httpvulnerable
// VULNERABLE — no CSP, no escaping
Content-Security-Policy: (missing)
// An injected <script> tag executes immediately.

// SAFE — strict CSP blocks inline scripts
Content-Security-Policy:
  default-src 'self';
  script-src 'self';  // inline scripts blocked!

// SAFE — nonce-based allowlisting
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-abc123xyz';
// <script nonce="abc123xyz">legitimate.js</script>
// Injected <script> without the nonce is blocked.
Mitigation

A practical defence checklist

Start with context-aware output encoding on every template that renders user data. Use your framework's auto-escaping by default and audit every bypass. Deploy a strict CSP with a nonce or hash-based script-src that blocks inline scripts and eval. Set default-src 'self' as a baseline and relax it only for specific, audited origins.

For rich-text features that require HTML, use a well-maintained sanitisation library like DOMPurify. Never attempt to filter HTML with regular expressions or blocklists — attackers have decades of encoding tricks to bypass them. Finally, adopt Trusted Types as an additional browser-level enforcement that prevents passing unsanitised strings to dangerous DOM sinks.

Further reading
  • CSP Reference — MDN Web Docs(MDN)
  • GitHub 2016 — CSP Mitigated Stored XSS(GitHub Blog)
  • DOMPurify — XSS Sanitisation Library(cure53)
  • Trusted Types — W3C Specification(W3C)
Key takeaways

What to remember

  • No single defence is sufficient — layer output encoding, CSP, and sanitisation.
  • A strict CSP with script-src 'self' or nonces blocks inline scripts even when escaping fails.
  • The GitHub 2016 case proved CSP can reduce a critical stored XSS to a non-issue.
  • Use DOMPurify for any feature that intentionally accepts HTML (rich text editors, markdown previews).
  • Trusted Types provide browser-level enforcement at the DOM sink — adopt them for new applications.

Knowledge check

0/3 answered · 0 correct
  1. 1.What is the role of Content Security Policy in XSS defence?

  2. 2.What happened when GitHub discovered a stored XSS in 2016?

  3. 3.Which technique should you use when your application needs to allow users to submit HTML (e.g. rich text)?