Why does adding a nonce-source to a Content-Security-Policy script-src make an 'unsafe-inline' token in that same list stop working?
answer
- one token switches another off
- two client generations, one source list
- presence, not position, in the list
- the inline check runs per type
- any nonce or hash suppresses 'unsafe-inline'
basics
~20 sA source list containing any nonce-source or hash-source makes a conforming browser ignore 'unsafe-inline' for that type. The weaker keyword is left in deliberately, so clients too old to understand the newer tokens still run the page.
solid answer
~40 sWhen a browser asks whether a source list allows all inline behaviour for a type, `'unsafe-inline'` counts only if that list contains no `nonce-source` and no `hash-source`. As soon as one appears, the keyword is inert: inline blocks run only if they are individually named by a nonce or a hash. That is not a quirk, it is the backwards-compatibility hinge of the whole mechanism. A policy can carry `script-src 'unsafe-inline' 'nonce-abc123'` and be read two ways at once - a current client enforces the nonce and ignores the keyword, while a client that predates the nonce grammar skips the token it cannot parse and falls back to `'unsafe-inline'`, so the page keeps working instead of going blank. Position in the list is irrelevant; only presence matters.
code
http · 3 linesHTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: script-src 'unsafe-inline' 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'; base-uri 'none'go deeper
Remember the headline: once a policy names a nonce or a hash, the 'unsafe-inline' keyword beside it does nothing. Inline code then runs only when the policy names it individually.
Explain the check behind it - all-inline is allowed only when the list holds the keyword and no nonce-source and no hash-source - and why writing both tokens is a deliberate dual-client fallback rather than a contradiction.
Show the operational half: switching a policy from the keyword to a nonce kills every inline block that has not been given the attribute, with no parse error and no header change to point at.
Weigh what the fallback row actually buys across an estate: an old client keeps a working page and no protection, so the combination is a compatibility decision with a stated cost, not a free one.
## The rule, stated exactly Deciding whether an inline block may run is not a scan for one keyword. A conforming browser runs a check - *"Does a source list allow all inline behavior for type?"* - and that check answers **yes** only when the list contains `'unsafe-inline'` **and** contains no `nonce-source` and no `hash-source`. Put a single nonce or a single hash into the list and the keyword stops contributing anything for that type. Two consequences fall straight out of the wording: - **Presence, not position.** `script-src 'unsafe-inline' 'nonce-abc123'` and `script-src 'nonce-abc123' 'unsafe-inline'` behave identically. There is no "last one wins" and no ordering rule. - **One is enough.** One nonce, or one hash, anywhere in that directive's source list, suppresses the keyword for the whole list. ## Why a policy would deliberately contain both Because a single source list is read by clients of different generations, and the older ones drop what they do not understand rather than failing. 1. A client that predates the nonce and hash grammar parses the list, does not recognise `'nonce-abc123'` as any known source expression, and discards it. What is left is `'unsafe-inline'`, so every inline block runs. The page is not protected, but it is not broken either. 2. A current client recognises the `nonce-source`, therefore ignores `'unsafe-inline'`, and runs only the blocks carrying the matching attribute. 3. Both clients get a working page from one header, and the stronger rule applies wherever it can be enforced. This is the same trick a source list plays with newer keywords generally: write the strong token for clients that understand it and leave the weak one behind it as the fallback the older client will land on. | Source list | Client without nonce support | Current client | |---|---|---| | `script-src 'unsafe-inline'` | every inline block runs | every inline block runs | | `script-src 'nonce-abc123'` | no inline block runs | only nonced blocks run | | `script-src 'unsafe-inline' 'nonce-abc123'` | every inline block runs | only nonced blocks run | The middle row is why the combination exists at all: a nonce-only policy is silently a blank page on an old client, and the third row buys a working page without weakening the current one. ## What the rule does not touch - **It is per type, per source list.** A `nonce-source` in `script-src` says nothing about `style-src`. If `style-src` carries `'unsafe-inline'` and no nonce or hash of its own, inline styles still run. - **It does not reach `'unsafe-eval'`.** That keyword governs turning strings into code, which is a different question from whether an inline block may run, and the presence of a nonce leaves it alone. - **It does not make the nonce optional.** Once the keyword is inert, an inline block with no matching nonce and no matching hash has nothing left to authorise it. - **It is not a parse error.** A list holding both tokens is perfectly well-formed; the browser simply gives one of them no effect. - **It applies the same way under an enforcing policy and a Report-Only one** - the difference between those two is whether the decision blocks or is merely recorded, not how the decision is reached. ## The mirror image, which is what actually bites Read the rule backwards and it is a deployment hazard: **adding a nonce to a policy that was relying on `'unsafe-inline'` breaks every inline block that does not carry the attribute, immediately and everywhere.** Nothing announces this. The header still parses, the keyword is still written in the list, and the only symptom is that inline handlers and inline blocks that worked yesterday no longer run. That is also the reason the rule is the centre of the whole nonce-and-hash idea rather than a footnote. `'unsafe-inline'` means "I cannot tell my own inline code from injected inline code, so run both". A nonce or a hash is the moment an application acquires that ability, and the specification treats the arrival of either one as the application's statement that it no longer needs the blanket permission. Leaving the keyword in the list is then a compatibility gesture aimed at clients that cannot honour the statement, not a hedge for the ones that can. ## What to say when asked Name the check, not just the outcome. "Any nonce or hash in the list makes `'unsafe-inline'` be ignored for that type, which is how one header serves old and new clients at once - and it means turning a nonce on breaks every inline block that has not been given the attribute." That single sentence covers the mechanism, the intent and the failure mode.
- Does the same suppression apply to 'unsafe-eval'?No. The rule belongs to the check that decides whether all inline behaviour is allowed for a type. `'unsafe-eval'` governs a different decision - turning a string into executable code - and a nonce or hash in the same list does not disturb it.
- A nonce is listed in script-src and 'unsafe-inline' in style-src. Do inline styles still run?Yes. The check runs against the source list that applies to the type in question. A nonce in `script-src` suppresses the keyword for script inline behaviour only; `style-src` is evaluated on its own list, which here still allows every inline style.
- Does it matter whether 'unsafe-inline' appears before or after the nonce-source?No. The check tests whether the list *contains* a nonce-source or hash-source, so ordering has no effect at all. Both spellings of the same list behave identically on every conforming client.
saying these in an interview costs you the question
- Thinks the two tokens combine, so nonced and unnonced blocks both run
- Says a list holding both tokens is a parse error
- Believes whichever token appears last in the list wins
- Claims leaving 'unsafe-inline' beside a nonce weakens the policy for current clients
- Assumes a nonce in script-src also silences 'unsafe-inline' in style-src
- Thinks adding a nonce is a safe no-op for existing inline blocks