On this page
Content Security Policy (CSP)
A CSP tells browsers which sources of script, style, image, and frame content to trust. A good policy stops most XSS and data-exfiltration attacks dead.
What it is
Content Security Policy is a response header that restricts which resources a page may load and execute. Level 3 is the current specification. The browser enforces the policy; any script, style, image, frame, or fetch from a source the policy does not allow is blocked, and a report is sent if a reporting endpoint is configured.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; report-to csp-endpoint
Why it matters
The single biggest class of web vulnerabilities is cross-site scripting. A solid CSP turns a successful XSS injection from “attacker runs JavaScript in your origin” into “browser blocks the script and logs a violation”. CSP also limits where data can be exfiltrated to, prevents your site from being framed, and disables dangerous legacy features like inline event handlers.
How to implement
Build a strict, nonce-based policy. The recommended pattern from Google’s strict CSP guidance:
Content-Security-Policy:
default-src 'self';
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
require-trusted-types-for 'script';
report-to csp-endpoint
That script-src looks self-contradicting: it lists 'unsafe-inline' and https:, the very things the mistakes below tell you to drop. The contradiction is deliberate and safe. Once 'strict-dynamic' and a nonce are present, a modern browser ignores both. The CSP Level 3 rule is that 'strict-dynamic' discards every host allowlist and 'unsafe-inline', leaving the nonce as the only thing that decides what runs. The https: and 'unsafe-inline' are there purely as a fallback for older browsers that predate nonces, giving them a weak policy rather than none. The nonce carries the real policy; the leftovers are for readers that cannot understand it. What you must never do is ship 'unsafe-inline' without a nonce and 'strict-dynamic': on its own it is not a fallback, it is the whole policy, and it permits exactly the injected inline script CSP exists to block.
Key directives:
default-src 'self'- fallback for all fetch directives. Allow your own origin by default.script-src- controls JavaScript. Use a per-response nonce; thestrict-dynamickeyword lets a trusted script load further trusted scripts.style-src- controls CSS. Same nonce model where possible.img-src,font-src,connect-src,media-src- list the third parties you actually use.frame-ancestors 'none'- replacesX-Frame-Options. See Clickjacking protection.object-src 'none'- kills Flash and plugin embeds.base-uri 'none'- blocks<base>tag injection attacks.report-to- endpoint that receives violation reports as JSON.
Generate a fresh nonce per response and embed it in every inline <script> tag.
Common mistakes
unsafe-inlineorunsafe-evalstanding alone. As above,'unsafe-inline'only degrades safely with a nonce and'strict-dynamic'beside it; by itself it neutralises the protection, and'unsafe-eval're-enableseval(). Use nonces or hashes.- Wildcards like
script-src *orhttps:alone. Almost as bad as no policy at all. - No
frame-ancestors. Leaves clickjacking open. - Forgetting the nonce on server-rendered inline scripts. The browser will block them and the page breaks.
- Shipping a report-only policy forever. Use
Content-Security-Policy-Report-Onlyto test, then switch to enforcing.
Nonce Lifecycle & Modern Edge Header Size Limits
Deploying strict CSP Level 3 nonces across SSR and edge networks requires handling several production architecture constraints:
- Per-Request Nonce Uniqueness: Nonces must be cryptographically secure (
crypto.randomBytes(16).toString('base64')) and strictly unique per HTTP response. Reusing nonces across multiple requests allows attackers who capture a nonce from one page load to bypass CSP on subsequent requests. - CDN Edge Header Size Limits (HTTP 431): Extensive CSP headers containing dozens of allowed domains, SRI hashes, and reporting directives can exceed CDN header size limits (e.g. Cloudflare’s 16KB limit or AWS CloudFront’s 8KB limit), triggering
431 Request Header Fields Too Largeerrors. Prefer'strict-dynamic'with nonces over massive domain allowlists to keep header sizes minimal. - Static Site Generation (SSG) Nonce Incompatibility: Pre-rendered SSG pages cannot generate per-request server nonces without serverless middleware. For static deployments, use SHA-256 script hashes (
'sha256-...') inscript-srcinstead of nonces, or inject nonces dynamically via edge workers (e.g. Cloudflare HTMLRewriter).
Verification
curl -sI https://example.com | grep -i content-security-policyshould return the header.- Run the page through Google CSP Evaluator.
- Open DevTools → Console. CSP violations appear as
Refused to load …errors. - Wire up a reporting endpoint and watch for unexpected blocks before tightening.