How does a client read script-src 'unsafe-inline' https: 'nonce-abcdefg' 'strict-dynamic' if it supports only CSP Level 1, only Level 2, or Level 3?
answer
- one list, three readings
- unknown tokens ignored, not fatal
- each generation drops what it cannot parse
- a nonce present disables inline-everything
- 'strict-dynamic' sheds scheme and host sources
basics
~20 sA Level 1 client ignores the tokens it cannot parse and enforces 'unsafe-inline' https:. A Level 2 client parses the nonce, which switches off inline-everything, leaving https: 'nonce-abcdefg'. A Level 3 client enforces 'nonce-abcdefg' 'strict-dynamic'.
solid answer
~40 sOne source list, read three ways, and two parsing rules do all the work. First, a source expression a client does not recognise is ignored rather than fatal - so a Level 1 client drops `'nonce-abcdefg'` and `'strict-dynamic'`, and a Level 2 client drops only `'strict-dynamic'`. Second, when a script source list contains a nonce-source, a hash-source or `'strict-dynamic'`, the list stops allowing all inline behaviour and `'unsafe-inline'` has no effect. So Level 1 enforces `'unsafe-inline' https:`; Level 2 enforces `https: 'nonce-abcdefg'`; Level 3 also honours `'strict-dynamic'`, beside which the scheme source, host sources and `'self'` no longer contribute, leaving `'nonce-abcdefg' 'strict-dynamic'`. Each generation converges on the strictest policy it can express, from one static header, with nothing to sniff.
code
http · 3 linesHTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: script-src 'unsafe-inline' https: 'nonce-abcdefg' 'strict-dynamic'; base-uri 'none'go deeper
Recall that a Content-Security-Policy travels as a response header and that a client quietly skips source expressions it does not understand instead of rejecting the policy.
Explain the two mechanics in order: unknown expressions are ignored, and a nonce-source, hash-source or 'strict-dynamic' in a script list stops that list allowing inline everything.
Read the list out generation by generation and name what each one actually enforces, then say what the oldest generation is still being handed and why that floor is acceptable in this traffic.
Decide how long the compatibility tokens stay. They are a migration device with a measurable cost in legibility, so tie their removal to client-population evidence rather than leaving them in forever.
## One header, three generations of client `Content-Security-Policy` is a **response** header, and the same bytes reach every client that asks for the page - a current one, one a few years old, and one embedded in a device that will never be updated again. A usage dashboard for an energy supplier has exactly that customer base. A directive's value is a whitespace-separated list of **source expressions**, and the specification deliberately lets one list degrade into a weaker but still meaningful policy on a client that understands less of it. The worked list is the specification's own: `script-src 'unsafe-inline' https: 'nonce-abcdefg' 'strict-dynamic'` ## The two parsing rules that produce the behaviour 1. **A source expression a client does not recognise is ignored.** It does not invalidate the directive and it does not invalidate the policy; the client enforces what it could parse and typically reports the rest as a warning. A client older than nonce-source grammar therefore drops `'nonce-abcdefg'`, and a client older than CSP Level 3 drops `'strict-dynamic'`. 2. **Some expressions switch off the blanket inline permission.** When a source list for a script type contains a nonce-source, a hash-source or `'strict-dynamic'`, that list no longer allows all inline behaviour, and `'unsafe-inline'` - present or not - has no effect on it. This is the rule that makes a compatibility token safe to leave in the list rather than a hole punched through the policy. ## Reading the list three times | Client understands | Ignores | Effective script-src | |---|---|---| | CSP Level 1 | `'nonce-abcdefg'`, `'strict-dynamic'` | `'unsafe-inline' https:` | | CSP Level 2 | `'strict-dynamic'` | `https: 'nonce-abcdefg'` | | CSP Level 3 | nothing in this list | `'nonce-abcdefg' 'strict-dynamic'` | - The **Level 1** client has no nonce grammar at all, so nothing switches the blanket permission off: the inline bootstrap runs because of `'unsafe-inline'`, and external script may come from any `https:` origin. That is the protection a policy could express in 2015. It is weak, and it is still more than no policy. - The **Level 2** client parses the nonce. Rule 2 fires, `'unsafe-inline'` stops having an effect, and the bootstrap runs only because its element carries the matching nonce-source value. External script is still bounded by the `https:` scheme source. - The **Level 3** client parses `'strict-dynamic'` as well. Rule 2 fires again, and beside that keyword the scheme source, host sources and `'self'` no longer contribute to the script decision - so the nonce is the single seed of trust and the list reduces to `'nonce-abcdefg' 'strict-dynamic'`. ## Why there is nothing to sniff Every generation converges on the strictest subset it can express, and the server learns nothing about the client to make that happen. There is no user-agent branch to get wrong, no feature detection, no second variant of the response, and no maintenance when a new client generation ships: the newest tokens are already in the list, waiting to be understood. That property is the whole design goal of ignoring an unrecognised source expression instead of rejecting the directive that contains it. ## The scoping limit `'strict-dynamic'` is defined for script types. Writing it into `style-src`, or scattering it through fetch directives at large, does not produce the same effect - in particular it does not switch inline style off, so a style list carrying `'unsafe-inline'` still allows all inline style whatever else is beside it. Keep the keyword where it means something. ## What the layering costs The compatibility is honest, and so is the floor: an out-of-date client really is served `'unsafe-inline' https:`, and you are choosing a weak policy for weak clients over none. That choice is only defensible while those clients exist in your traffic. When they no longer do, the two leading tokens are dead weight on a header every engineer has to read, and the list is meant to be shortened - the layering is a migration device, not a permanent shape.
- What does 'strict-dynamic' say about script that an already-trusted script inserts at runtime?Trust propagates programmatically: a script the policy already allowed - because its element carried a matching nonce-source or hash-source value - passes that trust to script elements it creates and inserts itself, and those need no nonce of their own. Script that the parser produced from markup in the document does not receive it, and the keyword is scoped to script types only.
- Why does this layering remove any need to detect the client's CSP level?Because an unrecognised source expression is ignored rather than fatal, every generation lands on the strictest subset it can parse from the same bytes. There is no branch to write, no variant response to cache, and nothing to update when a newer client appears.
- What changes if you keep 'unsafe-inline' but drop the nonce from the list?Level 1 and Level 2 clients then allow every inline block, because nothing in the list switches the blanket inline permission off - the compatibility token becomes a live permission. A Level 3 client still ignores it thanks to `'strict-dynamic'`, but now has no trusted seed script to propagate from.
A notice posted in several languages, where each reader obeys only the lines they can read - and the strictest lines are written so that anyone who can read them is bound by them.
saying these in an interview costs you the question
- Says an unrecognised source expression makes the whole policy fail
- Thinks the server picks a policy by reading the user agent
- Believes 'unsafe-inline' still applies once a nonce-source is present
- Assumes a Level 1 client rejects the nonce token as a syntax error
- Thinks different policies must be served to different client versions
- Puts 'strict-dynamic' in style-src expecting the same effect