What is a Content Security Policy?
A list of where your page is allowed to load things from. The strongest defence against script injection, and the easiest header to get wrong.
CSP is a header declaring which sources the browser may load scripts, styles, images and frames from. Anything from an undeclared source is blocked. It is the most effective single defence against cross-site scripting, and it requires knowing what your site actually loads.
The problem it solves
If an attacker gets a script onto your page — through a comment field, a compromised dependency, a vulnerable plugin — the browser runs it with full access to your page, your cookies and your users' sessions. CSP means the browser refuses to run it because it did not come from an approved origin.
It is defence in depth. You should still sanitise input; CSP is what limits the damage when something gets through anyway.
How it is written
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'self';
object-src 'none';
base-uri 'self'| Directive | Controls |
|---|---|
default-src | The fallback for anything not otherwise specified. |
script-src | Where JavaScript may load from. The most important one. |
style-src | Stylesheets. |
img-src | Images. |
connect-src | Where fetch, XHR and WebSocket connections may go. |
frame-ancestors | Who may embed your page. Replaces X-Frame-Options. |
object-src | Plugins. Set to 'none' — nothing legitimate needs it. |
base-uri | Prevents an injected <base> tag redirecting every relative URL. |
The directives that quietly defeat it
'unsafe-inline'in script-src. Allows any inline script, which is exactly what injection produces. This single value removes most of the protection. Use nonces or hashes instead.'unsafe-eval'. Permitseval()and string-to-code conversion. Some older libraries need it; treat it as a debt to remove.*as a source. Allows everything, which is the same as having no policy for that directive.- Broad CDN wildcards. Allowing an entire public CDN means allowing anything anyone has ever uploaded to it.
'unsafe-inline' in style-src is a much smaller risk than in script-src, and is a common pragmatic compromise on sites with inline styles. The one that matters is scripts.Rolling it out without breaking things
- Deploy
Content-Security-Policy-Report-Onlywith your intended policy and areport-uri. Nothing is blocked; violations are reported. - Collect for a week. You will find third-party scripts nobody remembered, an analytics tag added by marketing, and a font from a CDN.
- Widen the policy to cover the legitimate ones and remove the rest.
- Switch to enforcing mode.
- Keep the report endpoint. It becomes an alert when someone injects something, which is the point.
Using nonces
To keep inline scripts without 'unsafe-inline', generate a random nonce per response:
Content-Security-Policy: script-src 'nonce-r4nd0mV4lu3' 'strict-dynamic'
<script nonce="r4nd0mV4lu3">…</script>The nonce must be unpredictable and different on every response. A static one is worse than useless, because it looks like protection and provides none.
What our audit reports about this
Every item below is measured directly, not inferred. Run it against your own site and the result names the exact rule or header responsible.
- Whether a CSP is present, and whether it is enforcing or report-only.
- Which directives are declared and which fall back to
default-src. - Whether
unsafe-inline,unsafe-evalor wildcard sources weaken the policy. - Whether
frame-ancestors,object-srcandbase-uriare set.
For agents and scripts, the same measurement is at
/api/v1/security?url=yoursite.com —
see the API documentation.
Related questions
Is CSP hard to implement?
On a simple site, an afternoon. On a large site with many third-party integrations, a project. Report-only mode is what makes it tractable — it turns guesswork into a list.
Do I need CSP if I have no user input?
It still helps. It limits the damage from a compromised dependency, a hijacked CDN or a malicious browser extension injecting into your page. Those are not hypothetical.
What is strict-dynamic?
It lets a script you explicitly trusted load further scripts, without you enumerating every one. It makes nonce-based policies practical for sites with dynamic script loading.
Read next
What are security headers and which do I need?
Six headers, what each actually prevents, and a configuration you can paste and adjust.
ReadIs my website secure? How do I check?
What you can verify yourself in twenty minutes, what needs tooling, and what "secure" does and does not mean.
ReadI found an API key in my HTML. What now?
Rotate first, investigate second. Then work out whether the key ever needed to be in the browser at all.
ReadWhat is HSTS and should I enable it?
It forces browsers to use HTTPS for your domain. Almost always worth it — with two decisions that are genuinely hard to reverse.
Read