← All posts
content security policy setupCSPweb securityvibe codingsecure coding

Content Security Policy Setup: Secure Your Site Without Breaking It

O

OverMCP Team

Content Security Policy (CSP) setup is a critical step for securing your website against XSS and data injection attacks, but it's notorious for breaking functionality if done wrong. Here's the short answer: Start in report-only mode, test thoroughly, and use nonces or hashes for inline scripts instead of 'unsafe-inline'. This guide walks you through a safe CSP setup that won't take down your site.

What Is a Content Security Policy?

CSP is an HTTP header that tells the browser which sources of content are allowed to load on your page. It blocks malicious scripts, styles, and other resources. A typical CSP header looks like:

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

But if you have inline scripts or external resources, this strict policy will break them. That's why many developers avoid CSP altogether. Let's fix that.

Step 1: Start in Report-Only Mode

Before enforcing CSP, use Content-Security-Policy-Report-Only. This header tells the browser to report violations without blocking anything. You'll need a reporting endpoint (like a simple server or a service like report-uri.com).

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

Run your site, check the reports, and adjust the policy. Only switch to Content-Security-Policy when you're confident.

Step 2: Identify Your Resources

List all the sources your site loads:

  • Scripts (inline, external)
  • Styles (inline, external, style attributes)
  • Images (including data URIs)
  • Fonts
  • Frames
  • AJAX/fetch calls
  • WebSockets
  • Workers
  • For each, note the origin (e.g., https://cdn.example.com) and whether it's inline (e.g., <script>alert('hi')</script>).

    Step 3: Build Your Policy Incrementally

    Start with a strict base and add exceptions one by one. Example base:

    Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self';

    Handling Inline Scripts

    Avoid 'unsafe-inline'. Instead, use nonces or hashes. A nonce is a random token generated per request:

    <script nonce="abc123">
      // your inline script
    </script>

    Then in the header:

    Content-Security-Policy: script-src 'self' 'nonce-abc123';

    For static inline scripts, use a hash of the script content:

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

    Generate the hash by running the script through a SHA-256 hasher (there are online tools, or you can compute it yourself).

    Handling External Resources

    Add the exact origins:

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

    Avoid wildcards like *.example.com unless necessary. Be explicit.

    Handling `style` Attributes

    Inline styles (style="color: red") are blocked by style-src 'self'. To allow them, use 'unsafe-hashes' with a hash, or switch to CSS classes. If you must allow inline styles, use:

    Content-Security-Policy: style-src 'self' 'unsafe-inline';

    But this weakens security. Better to refactor your code.

    Step 4: Test with Real Browser Reports

    Browsers send violation reports to the report-uri (or report-to for newer CSP). Use a free service like report-uri.com or set up your own endpoint. Example report:

    {
      "csp-report": {
        "document-uri": "https://example.com/page",
        "blocked-uri": "https://evil.com/script.js",
        "violated-directive": "script-src 'self'"
      }
    }

    Check these reports daily during your setup phase.

    Step 5: Handle Common Third-Party Integrations

  • Google Analytics: script-src https://www.google-analytics.com https://ssl.google-analytics.com; img-src https://www.google-analytics.com https://ssl.google-analytics.com;
  • Facebook Pixel: script-src https://connect.facebook.net; img-src https://www.facebook.com;
  • Google Fonts: style-src https://fonts.googleapis.com; font-src https://fonts.gstatic.com;
  • CDN libraries: Add the exact CDN URL.
  • Step 6: Use CSP Evaluator Tools

    Google's CSP Evaluator (csp-evaluator.withgoogle.com) can check your policy for common misconfigurations. It's free and fast.

    Step 7: Deploy Gradually

  • Deploy report-only on a staging site.
  • Run automated tests (e.g., Playwright, Selenium) to catch breakage.
  • Deploy report-only to production and monitor reports for a week.
  • Switch to enforcement with a report-uri to catch any missed violations.
  • Common Pitfalls and Fixes

  • 'unsafe-inline' everywhere: Replace with nonces/hashes.
  • Missing `default-src`: Always set default-src 'self' as a fallback.
  • Forgetting `connect-src` for APIs: If your app makes fetch calls to an external API, add connect-src https://api.example.com.
  • WebSockets: Use connect-src ws://example.com wss://example.com.
  • Base64 images: Use img-src 'self' data: to allow data URIs.
  • Real Example: Fixing a Broken CSP in a Next.js App

    Suppose you have a Next.js app with an inline script for analytics. Instead of:

    Content-Security-Policy: script-src 'self' 'unsafe-inline';

    Use a nonce. In Next.js, you can generate a nonce in middleware:

    // middleware.js
    import { NextResponse } from 'next/server';
    
    export function middleware(request) {
      const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
      const cspHeader = `script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}';`;
      const requestHeaders = new Headers(request.headers);
      requestHeaders.set('x-nonce', nonce);
      const response = NextResponse.next({ request: { headers: requestHeaders } });
      response.headers.set('Content-Security-Policy', cspHeader);
      return response;
    }

    Then in your layout, set the nonce attribute on scripts:

    <script nonce={nonce} src="/analytics.js" />
    <script nonce={nonce}>
      // inline analytics code
    </script>

    How OverMCP Can Help

    If you're vibe-coding an app and want to automate CSP scanning (and other security checks), OverMCP scans your deployed site for common CSP misconfigurations and other vulnerabilities. It's designed for fast-moving developers who skip manual security reviews.

    FAQ

    What is the difference between Content-Security-Policy and Content-Security-Policy-Report-Only?

    Content-Security-Policy enforces the policy and blocks violations. Content-Security-Policy-Report-Only only reports violations without blocking, allowing you to test policies safely.

    How do I allow inline scripts without 'unsafe-inline'?

    Use nonces or hashes. Generate a unique nonce per request and add it to the script tag and header. For static scripts, compute a SHA-256 hash of the script content and use it in the header.

    What should I do if my CSP is breaking a third-party widget?

    Check the widget's documentation for its required CSP directives. If it needs 'unsafe-inline', consider replacing it with a more secure alternative. If not possible, add the specific origins it requires and use nonces for its inline scripts.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free