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.
CSP, context-aware escaping, sanitisation libraries, and the review checklist that catches what scanners miss. How to ship HTML that cannot execute attacker code.
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.
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.
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.
Toggle directives on and off, then test what happens when a script tries to execute.
Toggle directives and run a test to see CSP violations.
The dashboard simulates how CSP blocks or allows each script attempt.
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.
// 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.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.
script-src 'self' or nonces blocks inline scripts even when escaping fails.1.What is the role of Content Security Policy in XSS defence?
2.What happened when GitHub discovered a stored XSS in 2016?
3.Which technique should you use when your application needs to allow users to submit HTML (e.g. rich text)?