How to Set Up Content Security Policy (CSP)

Share
How-To Guide

How to Set Up Content Security Policy (CSP)

The most powerful security header - configured correctly

TL;DR

TL;DR (25 minutes)

Content Security Policy prevents XSS by controlling which resources can load. Start with Content-Security-Policy-Report-Only to test without breaking your site. Use default-src 'self' as your baseline, add nonces for inline scripts, and include third-party domains only where needed. Monitor violations with report-uri before switching to enforced mode.

Prerequisites

  • Ability to set HTTP headers (via server config, middleware, or hosting platform)
  • Understanding of what resources your app loads (scripts, styles, images, fonts)
  • Access to browser DevTools for testing
  • Optional: A reporting endpoint for CSP violations

What is Content Security Policy?

CSP is an HTTP header that tells browsers which sources of content are trusted. When a resource violates the policy, the browser blocks it and optionally reports the violation.

Why CSP Matters

Even if an attacker injects malicious code into your page (via XSS or compromised dependencies), CSP prevents it from executing if it doesn't match your policy. It's your last line of defense against script injection.

CSP Directive Reference

DirectiveControlsExample
default-srcFallback for all resource types'self'
script-srcJavaScript sources'self' 'nonce-abc'
style-srcCSS sources'self' 'unsafe-inline'
img-srcImage sources'self' data: https:
font-srcFont sources'self' https://fonts.gstatic.com
connect-srcXHR, WebSocket, fetch() URLs'self' https://api.example.com
frame-srciframe sources'self' https://youtube.com
frame-ancestorsWho can embed this page'none' or 'self'
object-srcFlash, plugins'none'
base-uri
<base></base>
tag URLs
'self'
form-actionForm submission URLs'self'

Step-by-Step Implementation

1

Start with Report-Only Mode

Use Content-Security-Policy-Report-Only to test without breaking your site:

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

This logs violations to the console and your reporting endpoint without blocking anything.

2

Build Your Base Policy

Start strict and add exceptions as needed:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self';
  font-src 'self';
  connect-src 'self';
  frame-src 'none';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
3

Add Third-Party Resources

Identify and allow external resources your app needs:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.jsdelivr.net https://www.googletagmanager.com;
  style-src 'self' https://fonts.googleapis.com;
  img-src 'self' data: https: blob:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com https://analytics.example.com;
  frame-src https://www.youtube.com https://player.vimeo.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
4

Implement Nonces for Inline Scripts

Instead of using 'unsafe-inline', use nonces:

Generate a unique nonce for each request:

// Express.js middleware
const crypto = require('crypto');

app.use((req, res, next) => {
  res.locals.nonce = crypto.randomBytes(16).toString('base64');
  next();
});

app.use((req, res, next) => {
  const nonce = res.locals.nonce;
  res.setHeader('Content-Security-Policy',
    `default-src 'self'; ` +
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; ` +
    `style-src 'self' 'nonce-${nonce}';`
  );
  next();
});

Add nonce to your inline scripts:

<!-- In your HTML template -->
<script nonce="<%= nonce %>">
  // This inline script will execute
  console.log('Authorized inline script');
</script>

<script>
  // This will be BLOCKED - no nonce
  console.log('Unauthorized script');
</script>
5

Use Hashes for Static Inline Scripts (Alternative)

For static inline scripts that don't change, use hashes:

# Generate hash of your inline script
echo -n "console.log('Hello');" | openssl dgst -sha256 -binary | openssl base64
# Output: 5jFwrAK0UV47oFbVg/iCCBbxD8X1w+QvoOUepu4C2YA=

Add the hash to your CSP:

Content-Security-Policy: script-src 'self' 'sha256-5jFwrAK0UV47oFbVg/iCCBbxD8X1w+QvoOUepu4C2YA=';
6

Set Up Violation Reporting

Create an endpoint to receive CSP violation reports:

// Express.js endpoint for CSP reports
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  const report = req.body['csp-report'];
  console.log('CSP Violation:', {
    blockedUri: report['blocked-uri'],
    violatedDirective: report['violated-directive'],
    documentUri: report['document-uri'],
    sourceFile: report['source-file'],
    lineNumber: report['line-number'],
  });

  // Send to your logging service
  // await logService.log('csp-violation', report);

  res.status(204).end();
});

Add report-uri to your CSP:

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

Modern reporting with report-to:

# Headers
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"/csp-report"}]}
Content-Security-Policy: default-src 'self'; report-to csp-endpoint;
7

Switch to Enforced Mode

Once violations are resolved, remove -Report-Only from the header:

# Before (testing)
Content-Security-Policy-Report-Only: default-src 'self'; ...

# After (enforced)
Content-Security-Policy: default-src 'self'; ...

CSP Security Checklist

  • Never use 'unsafe-inline' for script-src if possible - use nonces instead
  • Avoid 'unsafe-eval' unless absolutely required (some frameworks need it)
  • Always include object-src 'none' to block Flash/plugins
  • Use frame-ancestors 'none' or 'self' to prevent clickjacking
  • Include base-uri 'self' to prevent base tag hijacking
  • Test thoroughly in report-only mode before enforcing
  • Monitor violation reports after deployment

Common CSP Configurations

Strict CSP for Modern Apps

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{random}' 'strict-dynamic';
  style-src 'self' 'nonce-{random}';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.yoursite.com;
  frame-src 'none';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;

Next.js / React Application

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-eval' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: blob: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' wss: https:;
  frame-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'self';

Note: 'unsafe-eval' is needed for development hot reloading. Consider removing in production.

Static Marketing Site

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://www.google-analytics.com;
  frame-src https://www.youtube.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self' https://formsubmit.co;
  frame-ancestors 'none';
  upgrade-insecure-requests;

E-commerce with Payment Integration

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://js.stripe.com https://www.paypal.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https: blob:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.stripe.com https://api.yoursite.com;
  frame-src https://js.stripe.com https://www.paypal.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self' https://api.stripe.com;
  frame-ancestors 'none';

How to Verify It Worked

Method 1: Browser Console

  1. Open DevTools (F12)
  2. Go to Console tab
  3. Look for CSP violation errors (red text mentioning "Refused to...")
  4. Each violation shows what was blocked and why

Method 2: CSP Evaluator

Use Google's CSP Evaluator to analyze your policy for weaknesses.

Method 3: Security Headers Scanner

# Check your CSP header
curl -I https://yoursite.com | grep -i content-security-policy

Method 4: Test XSS is Blocked

Try injecting a script via URL parameters (if your app reflects user input):

# This should be blocked by CSP
https://yoursite.com/search?q=<script>alert('xss')</script>

Common Errors and Troubleshooting

Inline scripts blocked

  • Solution A: Add nonces to inline scripts
  • Solution B: Move inline scripts to external files
  • Solution C: Use 'unsafe-inline' (weakens security)

Third-party scripts blocked

  • Identify the domain: Check console for the blocked URL
  • Add to script-src: script-src 'self' https://cdn.example.com
  • Check subdomains: You might need https://*.example.com

Styles not loading

  • Inline styles: Add 'unsafe-inline' to style-src or use nonces
  • External stylesheets: Add the stylesheet domain to style-src
  • CSS-in-JS: Many libraries require 'unsafe-inline'

Images not loading

  • Base64 images: Add data: to img-src
  • External images: Add https: or specific domains
  • Blob URLs: Add blob: to img-src

API calls blocked

  • Fetch/XHR: Add API domain to connect-src
  • WebSockets: Add wss: and WebSocket URL to connect-src

Pro Tip: Iterative CSP Development

Start with the most permissive working policy, then gradually tighten it. Use report-only mode and violation logs to identify what needs to be allowed. Aim to remove 'unsafe-inline' and 'unsafe-eval' over time.

Frequently Asked Questions

What is Content Security Policy (CSP)?

CSP is an HTTP header that tells browsers which content sources are allowed on your page. It prevents XSS attacks by blocking unauthorized scripts, styles, images, and other resources from loading or executing.

Why does my CSP break my website?

CSP blocks content that doesn't match your policy. Common issues: inline scripts blocked (use nonces), third-party resources blocked (add their domains), or eval() calls blocked (add 'unsafe-eval'). Check browser console for specific violations.

Should I use 'unsafe-inline' in my CSP?

'unsafe-inline' significantly weakens XSS protection. Use nonces or hashes for inline scripts instead. For styles, 'unsafe-inline' is often needed for CSS-in-JS libraries, but migrate to external stylesheets when possible.

What is a CSP nonce and how does it work?

A nonce is a random, one-time value generated for each request. Add it to your CSP header (script-src 'nonce-abc123') and to each authorized inline script (nonce="abc123"). Only scripts with matching nonces execute.

What is the difference between report-only and enforced mode?

Content-Security-Policy blocks violations. Content-Security-Policy-Report-Only only reports them without blocking. Start with report-only to identify issues, then switch to enforced once your policy is correct.

Test Your CSP Configuration

Scan your site to check if CSP is configured correctly and identify potential weaknesses.

Start Free Scan
How-To Guides

How to Set Up Content Security Policy (CSP)