🛡️Security

Content Security Policy: The Header That Stops XSS Dead

Content Security Policy is one of the most effective XSS defenses available. Most sites don't have one. Here's how to add it without breaking your site.

7 min readFebruary 7, 2026By FreeToolKit TeamFree to read

XSS (Cross-Site Scripting) is consistently in OWASP's top 10 web vulnerabilities. The main defense is escaping user input. But bugs happen. A Content Security Policy is the defense in depth that limits damage when a bug slips through.

Starting With Report-Only

Never deploy CSP directly to enforcement on an existing site. Use report-only first:

Add a simple route to log violation reports. Run this for two weeks in production. Collect what would have been blocked. You'll see analytics scripts, fonts from Google, CDN resources, chat widgets — everything that needs to be explicitly allowed.

Building Your Actual Policy

Based on your violation report, add legitimate sources:

The Inline Script Problem

'unsafe-inline' for scripts defeats most of CSP's protection. Any XSS injection is also inline. If you need it, use nonces instead — random values that allow only your legitimate inline scripts.

For Next.js, the recommended approach is automatic nonce generation in middleware. Each page gets a fresh nonce, inline scripts are tagged with it, and the CSP header is set with that nonce. Injected scripts can't have a nonce they don't know in advance.

Quick Wins Before Full CSP

Even a loose CSP is better than none. Start with these headers, which are simpler to add and provide real protection:

  • X-Frame-Options: DENY — prevents clickjacking
  • X-Content-Type-Options: nosniff — prevents MIME type sniffing
  • Referrer-Policy: strict-origin-when-cross-origin — limits referrer data leakage
  • Permissions-Policy: camera=(), microphone=() — restricts powerful browser APIs

Frequently Asked Questions

What is Content Security Policy?+
Content Security Policy (CSP) is an HTTP response header that tells browsers which content sources are legitimate for your site. A CSP like 'script-src self' instructs the browser to only execute JavaScript loaded from your own domain. Any script injected by XSS (cross-site scripting) that loads from an attacker's domain gets blocked. CSP is a second line of defense against XSS — it limits the damage when an injection happens, even if your input sanitization missed something. A strict CSP also blocks inline scripts, which eliminates a large class of XSS vulnerabilities.
Why don't more sites implement CSP?+
Two reasons: complexity and fear of breaking things. A strict CSP that blocks inline scripts often breaks sites immediately because many third-party tools (analytics, chat widgets, advertising scripts) inject inline JavaScript. Implementing CSP properly requires auditing all script sources, moving inline scripts to external files or adding nonces/hashes, and carefully testing the policy before enforcing it. The process takes days for a complex site. Many teams look at the complexity and skip it. The result is the majority of websites lack CSP protection, even though XSS remains one of the most common web vulnerabilities.
What is the difference between CSP report-only mode and enforcement mode?+
Report-only mode (Content-Security-Policy-Report-Only header) evaluates your CSP and reports violations but doesn't block anything. Your site works normally, and you receive reports of what would have been blocked. This lets you test a policy before enforcing it — a critical step since enforcing a strict policy without testing first often breaks your own site. Enforcement mode (Content-Security-Policy header) actually blocks the violating content. The standard workflow: start with report-only and collect violations for 1-2 weeks, fix everything your policy would block, then switch to enforcement mode.
What is a nonce in CSP?+
A nonce (number used once) is a random value added to legitimate inline scripts to mark them as trusted. Your CSP says script-src 'nonce-abc123' and your legitimate inline script has <script nonce='abc123'>. The browser executes it because the nonce matches. An injected script from XSS won't have the nonce, so the browser blocks it. The nonce must be different on every page load and genuinely random — predictable nonces defeat the protection. Next.js, NuxtJS, and most modern frameworks support automatic nonce generation. This approach lets you use inline scripts legitimately while still blocking injected ones.

🔧 Free Tools Used in This Guide

FT

FreeToolKit Team

FreeToolKit Team

We build free browser-based tools and write practical guides that skip the fluff.

Tags:

securitydeveloperwebxsscsp