Content Security Policy: telling the browser what may run
A Content Security Policy (CSP) is a response header that lists where a page may load scripts, styles, images, frames and other resources from. The browser refuses anything else. When an attacker manages to inject a script into the page, a strict policy stops it from running, which makes CSP the second line of defence against cross-site scripting after output encoding.
Quick facts
- Header
- Content-Security-Policy, or Content-Security-Policy-Report-Only for testing
- Specification
- Content Security Policy Level 3 (W3C)
- Main protection
- Injected scripts do not run; resources load only from listed sources
- Also covers
- Who may frame the page (frame-ancestors) and where forms may submit
- Safe way to start
- Report-only mode, then enforce once reports are clean
Directive names follow MDN; the parent guide is HTTP security headers.
The CSP directives that matter most
| Directive | Controls | Typical value |
|---|---|---|
| default-src | Fallback for every resource type not listed separately | 'self' |
| script-src | Where scripts may load from, and whether inline scripts run | 'self' plus nonces or hashes |
| style-src | Where styles may load from | 'self' |
| img-src | Where images may load from | 'self' data: |
| connect-src | Where scripts may send requests | 'self' and your API origin |
| frame-ancestors | Which sites may put this page in a frame | 'self' or 'none' |
| form-action | Where forms may submit | 'self' |
| object-src | Plugins such as embedded objects | 'none' |
Directives not listed fall back to default-src, except a few such as frame-ancestors and form-action, which have no fallback and must be set on their own.
A Content Security Policy header example
A starting policy for a site that serves its own scripts and styles and calls one API might look like the one below. Replace the API origin with your own, and add sources as reports show what the site really loads.
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https://api.example.com; frame-ancestors 'self'; form-action 'self'; object-src 'none'; base-uri 'self'
Nonces, hashes and inline scripts
The protection against injection comes from refusing inline script, because injected script is almost always inline. A policy that allows 'unsafe-inline' for scripts gives most of that protection away.
If a page needs inline script, allow it by a nonce, a random value generated per response and repeated on the script tag, or by a hash of the script's exact content. The OWASP Content Security Policy Cheat Sheet describes a strict policy built this way.
Reading CSP violation reports
A policy can name an endpoint that receives a report every time the browser blocks something. Each report states the page, the directive that was violated and the blocked source, which makes it the main tool for building and maintaining a policy.
Expect noise. Browser extensions inject scripts and styles into pages, and their violations look like attacks until you recognise the sources. Group reports by blocked source and directive, deal with the largest groups first, and treat a new source on a sensitive page as worth a look.
Rolling out CSP without breaking the site
- Send the policy as report-only. Content-Security-Policy-Report-Only reports violations without blocking anything.
- Collect and read the reports. Each one names the directive and the blocked source.
- Fix the site or extend the policy. Move inline script to files or add nonces; add sources the site really needs.
- Enforce on a slice first. One section or a share of traffic, the same discipline as for any new block.
- Keep reporting on after enforcing. New third-party scripts and browser extensions keep producing reports.
This is the same order as introducing request filtering: observe first, then enforce, as on monitor mode.
Questions
What is Content Security Policy?
Content Security Policy is an HTTP response header that tells the browser which sources a page may load scripts, styles, images, frames and other resources from, so injected scripts do not run.
Does CSP prevent XSS?
It limits the damage. A strict policy without unsafe-inline stops most injected scripts from running, but output encoding remains the first defence.
How do I test a Content Security Policy?
Send it as Content-Security-Policy-Report-Only first, read the violation reports, fix or extend the policy, then enforce it.
What is a CSP nonce?
A random value generated for each response and placed both in the policy and on allowed script tags, so only those scripts run.