Content Security Policy Setup: Secure Your Site Without Breaking It
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-violationsRun 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:
style attributes)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
script-src https://www.google-analytics.com https://ssl.google-analytics.com; img-src https://www.google-analytics.com https://ssl.google-analytics.com;script-src https://connect.facebook.net; img-src https://www.facebook.com;style-src https://fonts.googleapis.com; font-src https://fonts.gstatic.com;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
report-uri to catch any missed violations.Common Pitfalls and Fixes
default-src 'self' as a fallback.connect-src https://api.example.com.connect-src ws://example.com wss://example.com.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.