Learn how to implement CSP headers to protect your web app from XSS attacks, with real-world examples and a step-by-step policy builder.
Cross-Site Scripting (XSS) is consistently in the OWASP Top 10. Content Security Policy (CSP) is the most effective browser-level defense — it tells the browser exactly which resources are allowed to load. This guide explains how to implement it without breaking your app.
CSP is an HTTP response header that instructs browsers to block or allow resources based on rules you define. A script from an unauthorized origin? Blocked. An inline <script> tag injected by an attacker? Blocked.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'
| Directive | Controls |
|---|---|
default-src | Fallback for all resource types not explicitly listed |
script-src | JavaScript sources (most important) |
style-src | CSS sources |
img-src | Image sources |
connect-src | XHR, fetch, WebSocket connections |
font-src | Web fonts |
frame-src | <iframe> sources |
form-action | Where forms can submit |
base-uri | Restricts <base> tag URLs |
upgrade-insecure-requests | Upgrades HTTP to HTTPS automatically |
'none' — Block everything for this directive
'self' — Same origin only
'unsafe-inline' — Allow inline scripts/styles (weakens CSP)
'unsafe-eval' — Allow eval() (avoid if possible)
https://cdn.example.com — Specific domain
https: — Any HTTPS URL
'nonce-{base64}' — Allow specific inline scripts with matching nonce
'sha256-{hash}' — Allow specific inline scripts by hash
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}' https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
connect-src 'self' https://api.yourapp.com https://vitals.vercel-insights.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
Nonces let you allow specific inline scripts without 'unsafe-inline'. Generate a new random nonce on every request:
// Next.js middleware
import { NextResponse } from 'next/server';
import crypto from 'crypto';
export function middleware(request) {
const nonce = crypto.randomBytes(16).toString('base64');
const csp = `
default-src 'self';
script-src 'self' 'nonce-${nonce}';
style-src 'self' 'unsafe-inline';
`.replace(/\s+/g, ' ').trim();
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
response.headers.set('x-nonce', nonce); // pass to RSC
return response;
}
// Use nonce on inline scripts in layout.tsx
<Script nonce={nonce} src="https://www.googletagmanager.com/gtag/js" />
Before enforcing, use Content-Security-Policy-Report-Only to detect violations without breaking anything:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /api/csp-report
This logs violations to your endpoint so you can fine-tune the policy before switching to enforcement.
report-only — observe violations for 1–2 weeksdefault-src 'self'Report-Only to Content-Security-Policy'unsafe-inline' for scripts — use nonces or hashes insteadUse the CSP Builder tool to visually construct and preview your policy, then test it with Google's CSP Evaluator.
A strict CSP is one of the highest-impact security improvements you can add to a web app with minimal user-visible change.