CSP Keywords & Source Expressions
Directives say what is being restricted. Source expressions say to what. They are shared across directives, so they live here rather than being repeated on each of the directive pages.
The single most common CSP mistake is a missing pair of single quotes.
'self', 'none', 'unsafe-inline' and the rest are keywords, and the
quotes are part of the syntax. Written without them they are parsed as host
names, which is why script-src self silently allows nothing and reports
everything.
Keywords
Section titled “Keywords”| Keyword | Meaning |
|---|---|
'self' |
The document’s own origin: same scheme, host and port. Does not cover data:, blob: or subdomains. |
'none' |
Nothing at all - but only when it is the only value in the directive. See below. |
'unsafe-inline' |
Inline scripts, inline styles and inline event handlers. Ignored when a nonce or hash is also present. |
'unsafe-eval' |
eval(), Function(), and setTimeout/setInterval called with a string. |
'wasm-unsafe-eval' |
WebAssembly compilation, without allowing JavaScript eval(). The narrower half of 'unsafe-eval'. |
'unsafe-hashes' |
Lets a hash source match an inline event handler or style attribute, which a plain hash does not. |
'strict-dynamic' |
Trust propagates from a nonced or hashed script to whatever it loads. Host allowlists are ignored. |
'report-sample' |
Include the first characters of the offending code in violation reports. |
'inline-speculation-rules' |
Allow inline <script type="speculationrules"> without allowing other inline script. |
'nonce-<base64>' |
A per-response random token, matched against the nonce attribute on an element. |
'sha256-<base64>' |
The base64 SHA-256 of an inline block’s exact contents. sha384 and sha512 are equally valid. |
'none' has to be alone
Section titled “'none' has to be alone”'none' means “no sources”, and the grammar allows it only as the entire
source list. Written alongside anything else it is neither a parse error nor an
override - it simply matches nothing, while the other sources go on matching:
Content-Security-Policy: default-src 'none' https://cdn.example.comThat policy allows https://cdn.example.com. A locked-down default with an
exception needs the exception in its own directive:
Content-Security-Policy: default-src 'none'; script-src https://cdn.example.com'self' is narrower than it looks
Section titled “'self' is narrower than it looks”'self' means the origin, and an origin is scheme, host and port together. It
does not cover www.example.com when you are on example.com, it does not
cover data: or blob: URLs, and it does not reliably cover wss:// for
connect-src in every browser. List those
explicitly.
Nonces
Section titled “Nonces”A nonce is a random value generated per response and repeated in two places: the policy, and the element.
Content-Security-Policy: script-src 'nonce-2726c7f26c'<script nonce="2726c7f26c"> window.APP_CONFIG = { region: "eu-west-1" };</script>Three rules make a nonce worth having, and breaking any of them makes it decorative:
- It must be fresh per response. A nonce baked into a static asset is a password that every attacker can read.
- It must be unpredictable - a cryptographically secure random value of at least 128 bits, base64-encoded.
- It must not be placed on markup an attacker can influence. A nonce copied onto
an injected
<script>is a nonce that authorises the injection.
A nonce in the directive causes 'unsafe-inline' in the same directive to be
ignored by any CSP Level 2 or later browser. That is deliberate, and it is how
you keep a fallback for browsers that do not understand nonces.
Hashes
Section titled “Hashes”A hash allows one exact inline block. The browser hashes the bytes between the tags and compares.
echo -n 'window.APP_CONFIG = { region: "eu-west-1" };' \ | openssl dgst -sha256 -binary \ | openssl enc -base64Content-Security-Policy: script-src 'sha256-bkfRdxp5pSKxNPI1rxXSef7NbXTGd8p2EQ8u1tn0i90='Hashes are whitespace-sensitive. Reformatting the file, or a template engine changing its indentation, invalidates the hash - which is why hashes suit generated inline blocks and nonces suit hand-written ones.
A plain hash does not cover inline event handlers or style attributes.
That is what 'unsafe-hashes' is for, and it is a step on a migration rather
than a destination - see
script-src-attr.
'strict-dynamic'
Section titled “'strict-dynamic'”'strict-dynamic' says: trust whatever an already-trusted script chooses to
load. It makes host allowlists, 'self' and 'unsafe-inline' irrelevant in
that directive, and it is what makes tag managers and code-split bundles
workable without listing every CDN.
Content-Security-Policy: script-src 'unsafe-inline' https: 'nonce-2726c7f26c' 'strict-dynamic'That policy is read differently by three generations of browser, which is the point:
- A CSP 1 browser sees
'unsafe-inline' https:. - A CSP 2 browser sees
https: 'nonce-2726c7f26c', because a nonce disables'unsafe-inline'. - A CSP 3 browser sees
'nonce-2726c7f26c' 'strict-dynamic', because'strict-dynamic'disables the host list.
'report-sample'
Section titled “'report-sample'”Without 'report-sample' the browser sends an empty sample on inline
violations. HeaderHawk groups inline issues by a hash of that sample, so a
policy without it collapses every inline violation on the site into a single
issue labelled unknown. Add it to script-src, script-src-attr, style-src
and style-src-attr before you start triaging inline violations.
Trusted Types violations are the exception: browsers include a sample for those regardless.
Schemes
Section titled “Schemes”A scheme source ends in a colon and allows every URL using it.
| Scheme | Allows |
|---|---|
https: |
Any HTTPS URL, from any host. Very broad - prefer specific origins. |
http: |
Any HTTP URL. Almost never correct on a secure site. |
data: |
data: URIs. Needed for inlined SVGs and icon fonts; unsafe for script. |
blob: |
blob: URLs, which bundlers and media players construct at runtime. |
mediastream: |
MediaStream sources. |
filesystem: |
FileSystem API URLs. |
wss: |
Secure WebSocket URLs, for connect-src. |
data: in script-src is the one to avoid outright: it lets any injected
markup carry its own payload, which defeats the directive.
| Pattern | Matches |
|---|---|
example.com |
That host, over any scheme and port. |
https://example.com |
That host over HTTPS only. |
https://example.com:443 |
That host, scheme and port exactly. |
*.example.com |
Any subdomain, but not example.com itself. |
https://example.com/app/ |
URLs under that path. A path ending in / is a prefix match. |
* |
Any URL with a network scheme. Not data:, blob: or inline code. |
Two details catch people out. *.example.com does not include the bare
domain, so a policy needs both if you serve from both. And a path in a source
expression is matched against the request URL, not the page’s - which makes
path restrictions useful for CDNs and useless for redirect-heavy origins, since
a redirect away from the allowed path is a violation.
Which keywords apply where
Section titled “Which keywords apply where”| Keyword | Applies to |
|---|---|
'unsafe-inline' |
script-src family, style-src family |
'unsafe-eval' |
script-src only |
'wasm-unsafe-eval' |
script-src only |
'unsafe-hashes' |
script-src, script-src-attr, style-src, style-src-attr - not the -elem pair |
'strict-dynamic' |
script-src, script-src-elem |
| Nonces and hashes | script-src family, style-src family |
'self', 'none' |
Every directive that takes a source list, including frame-ancestors and form-action |
frame-ancestors is the strictest about this:
it accepts only 'none', 'self', hosts and schemes. A nonce or
'unsafe-inline' there is a parse error’s worth of nothing.
See also
Section titled “See also”- CSP Directive Reference - one page per directive
- CSP Concepts - how a policy is put together
- Troubleshooting - when the policy is not doing what you expect