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