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.

Section Tools Updated Specification W3C CSP Level 3

Quick facts

Header
Content-Security-Policy, or Content-Security-Policy-Report-Only for testing
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

DirectiveControlsTypical value
default-srcFallback for every resource type not listed separately'self'
script-srcWhere scripts may load from, and whether inline scripts run'self' plus nonces or hashes
style-srcWhere styles may load from'self'
img-srcWhere images may load from'self' data:
connect-srcWhere scripts may send requests'self' and your API origin
frame-ancestorsWhich sites may put this page in a frame'self' or 'none'
form-actionWhere forms may submit'self'
object-srcPlugins 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.

http
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.

WAYS TO ALLOW SCRIPT OWN INLINE SCRIPT RUNS INJECTED INLINE SCRIPT BLOCKED NO PER-RESPONSE WORK 'UNSAFE-INLINE' ALLOWLIST OF HOSTS NONCE PER RESPONSE HASH OF THE SCRIPT // the first row runs your script and the attacker's
// four approaches, and which keep injected script out

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

  1. Send the policy as report-only. Content-Security-Policy-Report-Only reports violations without blocking anything.
  2. Collect and read the reports. Each one names the directive and the blocked source.
  3. Fix the site or extend the policy. Move inline script to files or add nonces; add sources the site really needs.
  4. Enforce on a slice first. One section or a share of traffic, the same discipline as for any new block.
  5. 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.