Content Security Policy (CSP): Writing a Real Policy

Content Security Policy is an HTTP header that tells browsers which sources of scripts, styles, images, and other resources they're allowed to load.

Content Security Policy: The Header That Neutralizes Most XSS Even When You Have a Bug

Content Security Policy (CSP) is a browser-enforced allowlist for resources — where scripts can come from, whether inline scripts are allowed, where forms can submit to, where iframes can embed. A well-configured CSP dramatically reduces the impact of XSS vulnerabilities: even if an attacker injects a script tag into your page, the browser refuses to execute it unless it matches your policy. CSP is one of the few security features where a single header change delivers outsized defense.

The directives that matter most

A minimum-viable strict CSP includes: default-src 'none' (deny by default), script-src with a nonce or hash-based allowlist, style-src (nonces or hashes; avoid 'unsafe-inline'), img-src (typically 'self' data: and your CDN), connect-src (your API origins), frame-ancestors 'self' (or specific parents, replaces X-Frame-Options), form-action 'self', base-uri 'self', object-src 'none'. Additional useful directives: upgrade-insecure-requests, report-uri or report-to for violation reports. Every additional 'unsafe-inline' or wildcard weakens the policy meaningfully; be deliberate about each exception.

Nonces vs. hashes vs. domains

Three ways to allow specific scripts. (a) Nonces — generate a random string per request, put it in the CSP header and on each allowed script tag. Cleanest for server-rendered apps; harder for static hosting. (b) Hashes — precompute SHA-256 hashes of allowed inline scripts, list them in the CSP. Good for static-generation frameworks. (c) Domain allowlists (script-src 'self' https://cdn.acme.com) — simple, but weak because any XSS-friendly endpoint on an allowed domain becomes an escape hatch. Nonces are the modern default; combine with strict-dynamic for allowlist-free trust propagation.

Report-only mode is your friend

Rolling out CSP breaks things — every third-party script, analytics tag, and inline handler you didn't know about generates a violation. Deploy with Content-Security-Policy-Report-Only header first, which reports violations without enforcing. Collect reports for 2-4 weeks, review, tighten the policy or fix the code, then flip to enforcement. Rolling out CSP in enforcement mode directly is a reliable way to break payment flows and lose sales on day one. Every serious CSP deployment I've seen goes through a report-only phase.

Third-party scripts are the hard part

Analytics, chat widgets, tag managers, A/B testing tools, and payment providers each need their domains added to script-src, connect-src, frame-src, etc. Every new vendor is a CSP change; every vendor's own script loading additional scripts from other domains is a further change. Google Tag Manager is particularly painful because it loads arbitrary third-party scripts by design — running GTM with strict CSP requires custom nonce injection. This tension is real; the answer is usually 'fewer third-party scripts,' not 'weaker CSP.'

Related headers to ship together

CSP is one of several security headers that should be set together. Strict-Transport-Security (HSTS) — forces HTTPS. X-Content-Type-Options: nosniff — prevents MIME confusion attacks. Referrer-Policy: strict-origin-when-cross-origin — controls what URL info is sent in Referer headers. Permissions-Policy — controls access to browser features (camera, microphone, geolocation). All of these are one-line additions with real security value; missing them is one of the most common findings in security audits.

Frequently asked questions

Will CSP break our third-party integrations?
Probably some, initially. That's what report-only mode is for. Most third-party vendors publish CSP guidance; where they don't, their required domains can be extracted from the report-only violation logs. Plan 1-2 weeks of iteration to reach a working strict CSP.
Do we need CSP if we're careful about XSS?
Yes. XSS bugs happen even in careful codebases (React's dangerouslySetInnerHTML, third-party markdown renderers, HTML email templates). CSP is the defense that works even when your primary defense has a bug. It's cheap defense in depth.
How strict is realistic for a large existing app?
Getting to a nonce-based strict-dynamic CSP is achievable for most apps in 2-8 weeks of concentrated work. Older apps with heavy inline handlers may need code refactoring first. Even a moderately-strict CSP (domain allowlist, no unsafe-eval) is much better than none.

Related fundraising guides (40)

Investor directory · Fundraising library · Articles A–Z · Company funding database