Glossary
Content-Security-Policy (CSP)
Content-Security-Policy is an HTTP response header telling the browser which sources a page may load scripts, styles, images and frames from, and what it may not do at all. It is the most effective single defence against cross-site scripting, because it stops injected code from executing even when an injection succeeds. It is also the only common security header that can break a working site, which is why it is deployed in report-only mode first.
How it works
The header lists directives, each naming allowed sources: `script-src 'self' https://cdn.example.com` permits scripts from the page's own origin and that CDN, and blocks everything else.
`default-src` provides the fallback for directives not stated explicitly.
`frame-ancestors` controls who may embed the page in an iframe — the modern replacement for X-Frame-Options.
Violations are blocked and reported to the console, and to a reporting endpoint if one is configured.
Why unsafe-inline defeats the purpose
`'unsafe-inline'` in `script-src` permits inline `<script>` blocks and inline event handlers — exactly the vectors CSP exists to block. A policy containing it for scripts provides very little protection while appearing to be configured.
The alternative is a nonce or a hash: the server emits a random nonce per response and marks its own inline scripts with it, so injected scripts — which cannot know the nonce — are blocked.
`'unsafe-inline'` for styles is a weaker concern and is frequently retained for practical reasons.
How to deploy it without an incident
Send `Content-Security-Policy-Report-Only` with the intended policy and a `report-uri` or `report-to` endpoint. Nothing is blocked; violations are reported.
Leave it long enough to cover real traffic — including the pages nobody tests and the third parties that load only on some journeys.
Resolve the violations: move inline scripts into files, or add nonces.
Switch the header to enforcing, and keep the reporting endpoint.
How to observe it
`curl -I https://example.com | grep -i content-security-policy` shows the header if present.
Read the value, not only its presence: a policy consisting of `default-src *` allows everything and is configuration theatre.
The browser console reports violations on the page you are looking at, which is the fastest way to see what a policy actually blocks.
The directives worth setting first
`frame-ancestors 'none'` or a short allowlist. It costs nothing on a site that is never embedded, it cannot break normal browsing, and it is the clickjacking defence.
`object-src 'none'`. Plugin content is obsolete, and blocking it removes a class of vector at no cost whatsoever.
`base-uri 'self'`. An injected `<base>` tag can redirect every relative URL on the page, and almost no site legitimately needs to set one.
`script-src` with a nonce, which is the directive that does the real work and also the one that takes the effort. Everything above can ship in an afternoon; this one wants the report-only period.
`upgrade-insecure-requests` as a transitional measure while mixed content is being cleaned up — a patch over the problem rather than a fix for it, but a useful one while the fix is in progress.
Frequently asked questions
- Does CSP stop all XSS?
- No. It makes injected script far harder to execute, which is why it is described as defence in depth. A policy with 'unsafe-inline' for scripts provides very little of that benefit.
- Is CSP required?
- No standard requires it. It is one of the highest-value optional headers, and the one most likely to break a site if enforced without a report-only period first.
- Does CSP replace X-Frame-Options?
- The frame-ancestors directive supersedes it and is more expressive. Sending both remains common for older browsers and is harmless.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website