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.
| 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.
| 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 |
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 |
| 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 | 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 |
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.
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.
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 |
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.
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'.
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.
'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. 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'.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.frame-ancestors, report-uri, sandbox or Report-Only, and the report-only header never works in a tag.Content-Security-Policy-Report-Only first. In the test it let the inline script run and still logged the violation.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.head, or send the header instead.'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.connect-src limits where fetch() may go; CORS decides if the response is readable