Introduction: The Web's Dirty Secret—Most Sites Are Still Unprotected
The web is a battlefield. Every day, attackers exploit Cross-Site Scripting (XSS), data exfiltration, and malicious third-party scripts to steal user data, hijack sessions, or deface websites. Yet, despite Content Security Policy (CSP) being around for over a decade, most developers either ignore it, misconfigure it, or treat it as an afterthought.
Why? Because CSP is hard. It's not a silver bullet—it's a strict, unforgiving rulebook that breaks things if you don't follow it to the letter. But here's the brutal truth: If you're not using CSP, you're leaving your users exposed. And if you're using it wrong, you might as well not use it at all.
This guide isn't about fluffy theory. It's about real-world CSP—what works, what fails, and how to implement it without shooting yourself in the foot. We'll cover:
- Why CSP is non-negotiable in 2026 (and why most tutorials lie to you)
- The anatomy of a CSP header (with battle-tested examples)
- Common mistakes that turn CSP into a paper tiger
- How to deploy CSP without breaking your site (yes, it's possible)
- The 20% of CSP rules that stop 80% of attacks
What CSP Actually Does (And What It Doesn't)
The Good: CSP as Your Last Line of Defense
Content Security Policy is an HTTP header that tells browsers which resources are allowed to load—scripts, styles, images, fonts, and more. Think of it as a bouncer for your website: if a resource isn't on the guest list, it doesn't get in.
Here's what CSP can do:
- Block inline scripts (
<script>alert('hacked')</script>), a major XSS vector. - Restrict third-party scripts (e.g., only allow
cdn.jsdelivr.netfor jQuery). - Prevent data exfiltration by blocking unauthorized
fetch()orXMLHttpRequestcalls. - Mitigate clickjacking with the
frame-ancestorsdirective.
But—and this is a big but—CSP does not:
- Replace input validation (sanitize your user input, period).
- Stop all XSS attacks (DOM-based XSS can still slip through).
- Work if misconfigured (a weak CSP is worse than no CSP).
The Ugly: Why Most CSP Implementations Fail
A 2025 study by PortSwigger found that over 60% of websites with CSP headers had policies so weak they provided no real protection. The biggest mistakes?
- Using
unsafe-inlineorunsafe-eval(which defeats the purpose). - Whitelisting entire domains (
script-src: https://cdn.example.com) instead of specific hashes or nonces. - Ignoring
report-uri(so you never know when attacks happen).
Example of a useless CSP:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com
This policy allows inline scripts, eval(), and all scripts from a CDN—making it trivial for attackers to bypass.
The Anatomy of a Bulletproof CSP Header
Directives You Actually Need (And Which Ones to Skip)
A strong CSP is specific, restrictive, and monitored. Here's a breakdown of the must-have directives:
| Directive | Purpose | Example Value |
|---|---|---|
default-src | Fallback for all other directives | 'self' |
script-src | Controls JavaScript sources | 'nonce-abc123' 'strict-dynamic' |
style-src | Controls CSS sources | 'self' 'unsafe-inline' (if needed) |
img-src | Restricts image sources | 'self' data: |
connect-src | Limits fetch(), XMLHttpRequest, and WebSocket connections | https://api.example.com |
frame-ancestors | Prevents clickjacking | 'none' or 'self' |
report-uri | Sends violation reports (critical for debugging) | https://your-logging-endpoint.com |
Nonces vs. Hashes: Which One Should You Use?
- Nonces (one-time tokens) are best for dynamic content:
<script nonce="abc123">console.log('Safe!');</script>Content-Security-Policy: script-src 'nonce-abc123' 'strict-dynamic' - Hashes are better for static scripts:
Pro Tip: UseContent-Security-Policy: script-src 'sha256-abc123...''strict-dynamic'with nonces to allow dynamically loaded scripts without whitelisting domains.
How to Deploy CSP Without Breaking Your Site
Step 1: Start in Report-Only Mode
Never roll out CSP directly. Use Content-Security-Policy-Report-Only first:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://your-logging-endpoint.com
This logs violations without blocking anything, so you can fix issues before enforcement.
Step 2: Monitor and Tighten
Use tools like:
- Report-URI (now part of Sqreen)
- Google's CSP Evaluator
- Chrome DevTools Console (look for CSP violation warnings)
Example of a violation report:
{
"csp-report": {
"document-uri": "https://example.com",
"blocked-uri": "inline",
"violated-directive": "script-src-elem",
"original-policy": "script-src 'nonce-abc123'"
}
}
Step 3: Gradually Enforce
Once violations drop to near-zero, switch to enforcement:
Content-Security-Policy: default-src 'self'; script-src 'nonce-abc123' 'strict-dynamic'; report-uri https://your-logging-endpoint.com
The 80/20 Rule of CSP—20% Effort for 80% Security
The 3 CSP Rules That Stop Most Attacks
- Block inline scripts (
'unsafe-inline'is evil). - Use nonces or hashes (never whitelist entire domains).
- Enable
report-uri(you can't fix what you can't see).
Example of an 80/20 CSP:
Content-Security-Policy: default-src 'self'; script-src 'nonce-abc123' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; report-uri https://your-logging-endpoint.com
What About Third-Party Scripts?
- Avoid them if possible (every external script is a risk).
- If you must use them, restrict to specific versions:
script-src https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js
5 Actionable Takeaways (Do These Now)
- Audit your current CSP (or lack thereof) with Google's CSP Evaluator.
- Deploy in
Report-Onlymode and monitor for 2 weeks. - Replace
unsafe-inlinewith nonces for all scripts. - Use
strict-dynamicto allow safe dynamic script loading. - Set up a
report-uriendpoint and act on violations.
Conclusion: CSP Is Hard, But the Alternative Is Worse
CSP isn't a magic shield—it's a disciplined, ongoing process. Most developers avoid it because it's inconvenient, but the cost of not using CSP is data breaches, compliance fines, and lost user trust.
Start small. Monitor. Tighten. Repeat. A well-configured CSP is the difference between a secure app and a hacker's playground.
Now, go audit your CSP—or implement one if you don't have it. Your users (and your future self) will thank you.