Content Security Policy (CSP) is a browser security mechanism that controls which resources a web page is allowed to load. By declaring a policy via HTTP header, you tell the browser exactly which scripts, styles, images, fonts, and connections are permitted. Anything not explicitly allowed is blocked.
CSP is one of the most effective defenses against Cross-Site Scripting (XSS) and data injection attacks.
How CSP Works#
CSP is delivered as an HTTP response header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
When the browser receives this header, it enforces the policy for the entire page. If a script tries to load from an unauthorized origin, the browser blocks it and logs a violation.
Key Directives#
| Directive | Controls |
|---|---|
default-src | Fallback for all resource types |
script-src | JavaScript sources |
style-src | CSS sources |
img-src | Image sources |
connect-src | XHR, fetch, WebSocket destinations |
font-src | Font file sources |
frame-src | Iframe sources |
media-src | Audio and video sources |
object-src | Plugin sources (Flash, Java) |
base-uri | Allowed <base> URLs |
form-action | Form submission targets |
frame-ancestors | Who can embed this page (replaces X-Frame-Options) |
Example Policies#
Strict policy — only allow resources from the same origin:
Content-Security-Policy: default-src 'self'
Allow a CDN for scripts and styles:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' https://cdn.example.com
Allow inline styles but not inline scripts:
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'
Nonces vs Hashes#
Instead of allowing 'unsafe-inline' (which defeats much of CSP’s purpose), use nonces or hashes to authorize specific inline scripts:
Nonce approach — generate a random value per request:
Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">
// This script is allowed because the nonce matches
</script>
Hash approach — allow a specific script by its SHA-256 hash:
Content-Security-Policy: script-src 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='
Nonces are generally preferred because they don’t require recomputing hashes when script content changes.
Report-Only Mode#
Test a policy without enforcing it:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports
The browser logs violations but doesn’t block anything. This lets you identify what would break before deploying the policy.
Common Mistakes#
- Using
'unsafe-inline'for scripts — this allows any inline script, including injected XSS payloads. Use nonces or hashes instead. - Using
'unsafe-eval'— allowseval(),Function(), and other dynamic code execution. Avoid unless absolutely necessary. - Overly broad wildcards —
script-src *orscript-src *.googleapis.commay include domains that host user-controlled content, creating bypass vectors. - Forgetting
default-src— if you don’t set a fallback, undeclared resource types have no restrictions. - Not using
object-src 'none'— Flash and other plugins are legacy attack vectors. Block them explicitly.
A Starter Policy#
A reasonable starting point for most sites:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
Then tighten it by removing 'unsafe-inline' for styles and adding nonces for any inline scripts.
For deeper analysis of CSP headers — parsing, finding bypasses, and scoring policies at scale — see my csp-toolkit post and the csp-toolkit library.
See also:
- csp-toolkit: Analyzing Content Security Policy Headers at Scale — my Python library for CSP analysis, bypass detection, and scoring
- What is the Common Weakness Enumeration (CWE)? — CSP mitigates CWE-79 (Cross-Site Scripting)