Cheatsheet

CSP Cheatsheet

This cheatsheet is a lookup for Content Security Policy (CSP): the header syntax, every directive family, the source keywords, and tested policies for nonces, hashes and strict-dynamic. It is for developers adding a CSP to stop injected scripts or fix a console error that says a resource violates a directive. The one confusion it clears up is that default-src is only a fallback for fetch directives, so it does not cover base-uri, form-action or frame-ancestors. The attack CSP limits is XSS.

Quick reference

Header forms

Header Behavior
`Content-Security-Policy` Enforces the policy and blocks violations
`Content-Security-Policy-Report-Only` Logs and reports violations, blocks nothing; header only
`<meta http-equiv="Content-Security-Policy">` Enforcing policy in markup; supports fewer directives

Syntax: directives are separated by ;, and each is a name followed by space-separated sources, as in default-src 'self'; img-src https://img.example.

Fetch directives

Directive Controls
`default-src` Fallback for every fetch directive that is not set
`script-src` JavaScript, including inline code and `eval()`
`script-src-elem`, `script-src-attr` `script` elements, and inline event handlers
`style-src` CSS; also `style-src-elem` and `style-src-attr`
`img-src` Images and favicons
`font-src` Web fonts
`connect-src` `fetch()`, XHR, WebSocket, EventSource
`media-src` Audio and video
`object-src` `object` and `embed` plugins
`frame-src` Nested browsing contexts such as `iframe`
`child-src` Workers and frames
`worker-src` Worker, SharedWorker and service worker scripts
`manifest-src` Web app manifest

Document and navigation directives

These do not fall back to default-src; set each one yourself.

Directive Controls
`base-uri` URLs allowed in the `base` element
`form-action` Targets of form submissions
`frame-ancestors` Who may embed this page; replaces `X-Frame-Options`
`sandbox` Applies an iframe-style sandbox to the page

Reporting directives

Directive Notes
`report-to` Names an endpoint defined by `Reporting-Endpoints`
`report-uri` Deprecated in the spec, but declare both until `report-to` is supported everywhere

Source expressions

Source Allows
`'none'` Nothing
`'self'` Same origin as the document
`https:` Any HTTPS URL (a scheme source)
`https://img.example` One host
`*.example.com` Any subdomain of a host
`'nonce-r4nd0m'` An inline `script` or `style` carrying `nonce="r4nd0m"`
`'sha256-...'` An inline block whose SHA-256 base64 digest matches
`'strict-dynamic'` Scripts loaded by an already trusted script
`'unsafe-inline'` Inline code; ignored when a nonce or hash is present
`'unsafe-eval'` `eval()` and similar string-to-code calls
`'unsafe-hashes'` Inline event handlers whose hash is listed

Common patterns

Start with a strict nonce policy

script-src 'nonce-Y8nQlP9sReTHu/APPxvGcg==' 'strict-dynamic'; object-src 'none'; base-uri 'none'
script-src 'nonce-ANnOeHK4saLeQvfB7GV7Rg==' 'strict-dynamic'; object-src 'none'; base-uri 'none'

Those are two real responses from a Node server using crypto.randomBytes(16).toString("base64") for each request. The nonce must differ on every response, so the HTML cannot be a static file.

Allow one inline script with a hash

const c = require("crypto");
const s = "window.ran=(window.ran||[]);window.ran.push('inline')";
console.log("sha256-" + c.createHash("sha256").update(s).digest("base64"));

Output:

sha256-b/LJTZrfVKluHDoX1cRbfpAAreTdxHDopXMGoyyAeJ8=

Put that string in script-src inside single quotes. The hash covers the exact characters between the tags, whitespace included, so any edit breaks it. In the test the matching inline script ran and a second, different inline script was blocked.

Read what each policy blocked

Measured results from a Chromium 153 page, listing which scripts ran:

Policy Page content Ran
`default-src 'self'` inline script, `onclick`, same-origin file only the file
`script-src 'nonce-r4nd0m'` nonce script, plain inline only the nonce script
`script-src 'sha256-b/LJ...'` hashed inline, other inline only the hashed one
`script-src 'nonce-abc'` nonce loader adds an unnonced file loader only
`script-src 'nonce-abc' 'strict-dynamic'` same page loader and file
`script-src 'unsafe-inline' 'nonce-abc'` nonce script, plain inline only the nonce script
`script-src 'self'` file calling `eval()` eval blocked
Report-Only `script-src 'self'` inline script ran, violation logged

Know the error text

Executing inline script violates the following Content Security Policy directive 'default-src 'self''. Either the 'unsafe-inline' keyword, a hash ('sha256-b/LJTZ...'), or a nonce ('nonce-...') is required to enable inline execution.
Executing inline event handler violates the following Content Security Policy directive 'default-src 'self''.
Loading the image 'http://localhost:8082/pic.png' violates the following Content Security Policy directive: "img-src https://img.example". The action has been blocked.

Chromium prints the hash that would allow the blocked inline script, which saves computing it. The unsafe-eval case gave this exception text: Evaluating a string as JavaScript violates the following Content Security Policy directive because 'unsafe-eval' is not an allowed source of script.

Confirm base-uri is not covered by default-src

default-src 'self'                      -> link resolves to http://other.test/x/rel
default-src 'self'; base-uri 'none'     -> link resolves to http://localhost:8082/rel

A base element pointing to another host was accepted under default-src alone, so relative links went to the attacker's host. Add base-uri 'none' or 'self'.

Stop other sites framing you

Content-Security-Policy: frame-ancestors 'self'

A parent on another origin got a blocked frame with Framing 'http://localhost:8082/' violates the following Content Security Policy directive: "frame-ancestors 'self'". Inside a meta tag the same directive printed 'frame-ancestors' is ignored when delivered via a <meta> element.

Pitfalls

  • Adding 'unsafe-inline' next to a nonce and expecting both to work: with a nonce or hash present, browsers ignore 'unsafe-inline', so only the nonced script ran. This is useful for fallback in older browsers, not for widening the policy.
  • Reusing one nonce on every response: a fixed nonce is a value an attacker can learn and reuse. Generate it per request from a secure random source and insert it with the template engine.
  • Hashing a script and then editing it: one added space changes the digest. Hash-based policies suit static builds; use nonces for pages you render per request.
  • *Putting or a broad CDN in script-src:** long host allowlists are hard to maintain and often include domains that host unsafe scripts, as MDN notes. Prefer nonces with 'strict-dynamic'.
  • Forgetting object-src 'none' and base-uri: default-src does not cover base-uri, as the run above shows, so a strict policy still needs both set.
  • Expecting a meta tag to match the header: meta delivery cannot carry frame-ancestors, report-uri, sandbox or Report-Only, and the report-only header never works in a tag.
  • Breaking a site by enforcing too early: deploy Content-Security-Policy-Report-Only first. In the test it let the inline script run and still logged the violation.
  • Inline styles: default-src 'self' also blocks style attributes and style elements. In the test a div with style="width:123px" and a style block setting height:77px both logged Applying inline style violates the following Content Security Policy directive, and neither applied. Add style-src with a nonce or move CSS into files.
  • Placing the meta tag late in the head: a policy in a meta element does not apply to content that comes before it. Put the tag first in head, or send the header instead.
  • Leaving 'unsafe-eval' in for one old library: it re-enables eval() for the whole page. Replace the library, or isolate it. Without the keyword, the test above blocked eval() with an exception.
  • Copying a policy from a blog without reading the console: every blocked load logs a line naming the directive and the blocked URL. Use those lines as the to-do list, and add the narrowest source that fixes each one.

Related ZipKit tools

  • CSP Header Builder — builds and validates a strict policy directive by directive, so you do not hand-write the semicolons

Related cheatsheets