Content Security Policy from scratch
Content Security Policy (CSP) is an HTTP response header that tells the browser which sources of scripts, styles, images and other resources are allowed to load on a page. Its main practical benefit is cutting off the impact of a successful XSS injection — even if an attacker manages to insert a `<script>` tag, a correctly configured CSP can stop the browser from executing it.
The basic shape
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none';Each directive controls a specific category of resource. `default-src` is the fallback for any directive you don't specify explicitly, but it's worth setting most categories explicitly rather than relying on the fallback, since it's easy to forget which categories inherit from it.
The directives that matter most
- **`script-src`** — the single most important directive for XSS mitigation. Restrict it to your own origin and any script CDNs you actually use; avoid `'unsafe-inline'` here if at all possible, since it defeats most of the point (an injected inline `<script>` becomes allowed too).
- **`style-src`** — similar idea for stylesheets; less security-critical than script-src, since CSS injection alone is a much weaker attack vector, but still worth restricting.
- **`img-src`, `font-src`, `media-src`** — usually just about limiting where assets load from, not primarily an XSS defense.
- **`connect-src`** — controls where `fetch`, `XMLHttpRequest`, WebSocket and EventSource connections can go. Missing this is a common way exfiltration via injected code still succeeds even when `script-src` looks locked down, because the injected code was allowed to run under a permissive `script-src` but the policy didn't block where it could send data.
- **`frame-ancestors`** — replaces the old `X-Frame-Options` header for controlling who can embed your page in an `<iframe>`; `'none'` is the strongest anti-clickjacking setting.
- **`object-src 'none'`** — Flash/plugin content is a legacy attack surface; disabling it entirely is safe on virtually every modern site.
`'unsafe-inline'` and `'unsafe-eval'` are named honestly
`'unsafe-inline'` allows inline `<script>` blocks and `onclick=`-style handlers to execute; `'unsafe-eval'` allows `eval()`, `new Function()`, and similar dynamic code execution. Both directly undermine CSP's main value against XSS, because they're exactly the mechanisms an injected payload would use. If your codebase relies on inline scripts, the better fix is to move them into external files or use nonces/hashes (below), not to blanket-allow inline execution.
Nonces and hashes: inline scripts without `'unsafe-inline'`
If you genuinely need an inline `<script>` block (some server-rendered apps do, for a bootstrap payload), CSP lets you allow it specifically rather than allowing all inline scripts:
Content-Security-Policy: script-src 'self' 'nonce-r4nd0mBase64Value';<script nonce="r4nd0mBase64Value">
window.__INITIAL_STATE__ = { /* ... */ };
</script>The nonce must be a fresh, unpredictable value generated per response (not hardcoded), or an attacker who can inject a `<script nonce="...">` tag with a guessed or leaked nonce defeats the protection. A hash-based alternative (`'sha256-...'`) works similarly but requires the exact script content to match a precomputed hash, which is more brittle if the inline content changes.
Roll it out with report-only mode first
Deploying a strict CSP straight to production risk breaking legitimate functionality you didn't account for (a forgotten CDN, a third-party widget). `Content-Security-Policy-Report-Only` lets the browser evaluate the policy and send violation reports without actually blocking anything:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports;Run in report-only mode, collect violations for a realistic period covering your traffic patterns, tighten the policy based on what legitimately needs to be allowed, then switch to the enforcing header.
Common mistakes
- Setting `default-src *` "to be safe," which allows nearly everything and provides close to no protection.
- Adding `'unsafe-inline'` to make a console warning go away without understanding what it disables.
- Forgetting `connect-src`, leaving a route open for data exfiltration even when script sources look locked down.
- Not testing report-only first, and instead discovering the policy blocked payment or analytics scripts in production.
Building one
Rather than hand-writing directives and hoping the syntax is right, the [CSP builder](/tools/security/csp-builder) generates a policy from a checklist of what your page actually needs, and the [security headers auditor](/tools/security/security-headers-auditor) checks a live URL's response headers, including CSP, against current best practice.