paulserban.eu

Writing Edition

Paul Serban

Writing, snippets & book notes

Content Security Policy (CSP): A Brutally Honest Guide to a More Secure Web

Why Most Websites Are Still Vulnerable-and How CSP Can Actually Fix It (If You Use It Right)

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:

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:

But-and this is a big but-CSP does not:

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?

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:

DirectivePurposeExample Value
default-srcFallback for all other directives'self'
script-srcControls JavaScript sources'nonce-abc123' 'strict-dynamic'
style-srcControls CSS sources'self' 'unsafe-inline' (if needed)
img-srcRestricts image sources'self' data:
connect-srcLimits fetch(), XMLHttpRequest, and WebSocket connectionshttps://api.example.com
frame-ancestorsPrevents clickjacking'none' or 'self'
report-uriSends violation reports (critical for debugging)https://your-logging-endpoint.com

Nonces vs. Hashes: Which One Should You Use?

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:

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

  1. Block inline scripts ('unsafe-inline' is evil).
  2. Use nonces or hashes (never whitelist entire domains).
  3. 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?

5 Actionable Takeaways (Do These Now)

  1. Audit your current CSP (or lack thereof) with Google's CSP Evaluator.
  2. Deploy in Report-Only mode and monitor for 2 weeks.
  3. Replace unsafe-inline with nonces for all scripts.
  4. Use strict-dynamic to allow safe dynamic script loading.
  5. Set up a report-uri endpoint 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.