πŸ›‘οΈ Content Security Policy (CSP): A Complete Guide for Developers



This content originally appeared on DEV Community and was authored by Fazal Mansuri

Security is one of the most critical aspects of modern web development. Even if your code is correct, attackers may exploit vulnerabilities like Cross-Site Scripting (XSS) or Clickjacking. That’s where Content Security Policy (CSP) comes in.

This blog covers:
✅ What CSP is & how it works
✅ All major directives (default-src, script-src, img-src, etc.) explained in detail
✅ Real-world examples
✅ Best practices & common pitfalls

Let’s dive in. 🔥

⚙ What is CSP & Why Do We Need It?

  • Content Security Policy (CSP) is an HTTP response header that defines where browsers can load resources (scripts, images, styles, fonts, iframes, workers, etc.) from.
  • Its main purpose: prevent XSS attacks by restricting execution of malicious scripts.
  • Without CSP, if an attacker injects a script into your page, the browser executes it blindly. With CSP, you can whitelist only trusted domains, blocking everything else.

Example CSP header:

Content-Security-Policy: default-src 'self'; img-src https://cdn.example.com; script-src 'self' https://apis.google.com;

This means:

  • By default, only load resources from the same origin ('self').
  • Allow images from cdn.example.com.
  • Allow scripts from self and Google APIs.

⚠ Important:
If default-src is missing, everything is allowed, weakening your CSP.

📌 Important Directives

Here’s a breakdown of commonly used CSP directives:

🔑 default-src

The fallback for all other directives (if they are not explicitly defined).

Content-Security-Policy: default-src 'self';

If script-src or style-src is missing, browsers fall back to default-src. ✅

📜 script-src

Defines where JavaScript can be loaded from.

Content-Security-Policy: script-src 'self' https://apis.google.com;
  • Special values:

    • 'self': allow only current domain
    • 'unsafe-eval': allow eval() usage (⚠ dangerous)
    • 'unsafe-inline': allow inline scripts (⚠ not recommended)
  • Better options:

    • Nonce β†’ Random value per request:
    <script nonce="abc123">console.log("Safe!");</script>
    

    With CSP:

    Content-Security-Policy: script-src 'self' 'nonce-abc123';
    
    • Hash β†’ Allow only exact inline script:
    <script>alert("hello")</script>
    

    With CSP:

    Content-Security-Policy: script-src 'self' 'sha256-XYZhashHere...';
    

✅ Nonce & Hash are safer than 'unsafe-inline'.

🎨 style-src

Controls where CSS can be loaded from.

Content-Security-Policy: style-src 'self' https://fonts.googleapis.com;

⚠ Using 'unsafe-inline' allows inline styles β†’ risky.
🔑 Instead:

  • Use nonce or hashes for dynamic styles.
  • Example:
  <style nonce="abc123">.btn { color: red; }</style>

🖼 img-src

Defines allowed image sources.

Content-Security-Policy: img-src 'self' https://cdn.example.com data:;

👉 data: allows base64-encoded images.

📄 font-src

Controls allowed font sources.

Content-Security-Policy: font-src 'self' https://fonts.gstatic.com;

🔗 connect-src

Defines where AJAX/fetch/WebSocket connections can be made.

Content-Security-Policy: connect-src 'self' https://api.example.com;

🎵 media-src

Specifies allowed sources for <audio> and <video>.

Content-Security-Policy: media-src 'self' https://media.example.com;

🏗 frame-src vs frame-ancestors

  • frame-src: Controls what your app can embed in an <iframe>.
  Content-Security-Policy: frame-src https://www.youtube.com;
  • frame-ancestors: Controls who can embed your site.
  Content-Security-Policy: frame-ancestors 'none';

✅ Use frame-ancestors to prevent clickjacking.

🧩 worker-src

Defines allowed sources for Web Workers & Service Workers.

  • Web Workers: Run scripts in a separate thread (background tasks).
  • Service Workers: Proxy requests for offline support / caching.
Content-Security-Policy: worker-src 'self' https://cdn.example.com;

📂 object-src

Restricts plugins like Flash/Silverlight (legacy). Best to set 'none'.

Content-Security-Policy: object-src 'none';

📝 Reporting with CSP

Instead of enforcing, you can test with reporting only:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report-endpoint;

This way, violations are logged but not blocked. Useful before rollout.

💡 Real-World Example

Imagine your webapp loads:

  • Scripts from your domain + Google APIs
  • Styles from your domain + Google Fonts
  • Images from CDN

CSP could look like:

Content-Security-Policy: default-src 'self'; script-src 'self' https://apis.google.com; style-src 'self' https://fonts.googleapis.com; img-src 'self' https://cdn.example.com; connect-src 'self' https://api.myapp.com; object-src 'none'; frame-ancestors 'none'

This protects you from attackers injecting malicious scripts/images from untrusted domains.

🚨 Common CSP Issues Developers Face

  • ❌ Images not loading β†’ Forgot img-src domain.
  • ❌ Fonts breaking β†’ Missing font-src.
  • ❌ Videos not playing β†’ media-src missing.
  • ❌ API calls failing β†’ Missing connect-src.
  • ❌ App not embeddable β†’ Strict frame-ancestors.

👉 If your frontend/backend look fine but still break, check CSP first.

✅ Best Practices

  • Always define a strict default-src.
  • Avoid 'unsafe-inline' and 'unsafe-eval' unless absolutely necessary – use nonces or hashes.
  • Use 'self' wherever possible.
  • Test with Content-Security-Policy-Report-Only before enforcing.

📌 Conclusion

CSP is not just another headerβ€”it’s a powerful security shield 🛡 for your apps.
The right configuration prevents XSS, clickjacking, and data injection while keeping performance intact.

👉 The bottom line: CSP = Control + Security + Peace of Mind.


This content originally appeared on DEV Community and was authored by Fazal Mansuri