In a nonce-based Content-Security-Policy, which rules stop injected markup from capturing or reusing a legitimate nonce value?
answer
- the value stops being an attribute
- selectors read attributes, not slots
- half-open tags swallow markup
- a duplicate attribute is also a refusal
- relative src resolves against base
basics
~20 sThree: the value is moved into the element's [[CryptographicNonce]] slot and the nonce content attribute is blanked, so attribute readers see nothing; the 'Is element nonceable?' check refuses a nonce on markup that looks like a half-open tag; and base-uri closes nonce retargeting.
solid answer
~50 sA nonce only works while the injected content cannot obtain it, so three separate rules guard it. First, once the element is browsing-context connected under a header-delivered policy, the value is kept in the element's `[[CryptographicNonce]]` internal slot and the `nonce` content attribute is set to the empty string - the page's own code still reads the value, but anything that reads *attributes*, such as a style rule selecting on attribute values, gets nothing to exfiltrate. Second, the *Is element nonceable?* check refuses to honour a nonce when an attribute's name or value contains a fragment of a start tag such as `<script`, or when the element had a duplicate-attribute parse error - the dangling-markup case where an unterminated attribute swallows a legitimate nonce. Third, `base-uri 'none'` stops an injected `<base>` retargeting a nonced element's relative `src`.
code
html · 7 lines<!-- injected fragment: the attribute value is never closed -->
<script src="//third-party.example/x.js" a="
<!-- the page's own element is consumed into that attribute -->
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
startStepTwo();
</script>go deeper
Know that a nonce is only useful while injected content cannot obtain it, and that the value disappears from the markup once the element is connected rather than sitting there for anything to read.
Explain the two element-level rules: the value moves into an internal slot and the attribute is blanked, and a nonce is not honoured on an element whose attributes carry a start-tag fragment or a duplicate-attribute parse error.
Demonstrate the third route - an injected base element retargeting the application's own nonced relative src while the policy still matches - and put base-uri in the policy as a matter of course.
Judge the residual risk honestly: three mechanisms protect the value, none protects a page whose markup surfaces are unbounded, so the policy's worth tracks how many places can emit markup at all.
## The attack these rules answer A nonce-based policy rests on one assumption: the attacker cannot learn or capture the value for this response. Injected markup gets to run inside the same document, so three distinct routes at that assumption exist, and the specifications close each one separately. 1. **Read the value and reuse it.** If injected content can observe the nonce - or get something else to observe it - it writes its own element carrying the same value and the policy authorises it. 2. **Swallow the value.** The injected fragment does not need to read the nonce if the HTML parser can be tricked into folding a legitimate nonced element into the attacker's own tag. 3. **Keep the value and change what it points at.** The nonced element stays exactly as the application wrote it, and the attacker changes what its relative URL resolves to. ## Rule one: the value stops being an attribute When an element with a nonce becomes browsing-context connected, and the document's policy list contains a header-delivered policy, the value is preserved in the element's `[[CryptographicNonce]]` internal slot and the `nonce` **content attribute** is set to the empty string. Why that matters: - Attribute values are readable by things that are not script. A style rule can select on an attribute's value and cause a fetch whose URL encodes what it matched, which turns the nonce into something an injected *stylesheet* can exfiltrate character by character without any script running at all. - Serialising the markup of the document, or reading the attribute back, likewise sees an empty string. - The value is not lost: the internal slot backs the element's `nonce` IDL attribute, so the page's own code still reads it. So the defence is precise - it removes the value from the **attribute surface** while leaving it available where the application needs it. It is not a general secrecy guarantee. ## Rule two: "Is element nonceable?" Before a nonce is honoured at all, the element is put through a check that can return *not nonceable*, in which case the nonce is disregarded however well it matches the policy. The two cases worth carrying in your head: - **An attribute whose name or value contains a fragment of a start tag**, such as `<script`. This is the dangling-markup case. Injected content leaves an attribute value unterminated; the parser keeps consuming markup into that attribute, and the legitimate `<script nonce="...">` that follows is absorbed into the attacker's element - which ends up holding a nonce it was never given. Refusing to honour a nonce on an element whose attributes contain such a fragment removes the prize. - **A duplicate-attribute parse error on that element.** Markup that produced one is markup the parser had to recover from, which is the shape these tricks take, so the nonce is not honoured there either. The check is a targeted defence against markup that the parser had to repair, not a general inspection of the element's content. ## Rule three: retargeting through an injected base element The third route leaves the nonce entirely alone. A `<base>` element changes the base URL that relative URLs in the document resolve against. Inject one, and a perfectly legitimate `<script nonce="..." src="steps/step2.js">` written by the application now resolves somewhere else. The nonce still matches, the policy still allows the element, and the bytes come from the attacker. The answer is the `base-uri` directive, which has nothing to do with fetches and everything to do with this: `base-uri 'none'` means no `<base>` element may take effect at all, so relative URLs keep resolving against the document's own URL. It costs nothing on a page that has no `<base>` of its own, which is why it belongs in essentially every nonce-based policy. | Route | What the attacker gets | What closes it | |---|---|---| | Read the value | a nonce to put on their own element | the attribute is blanked into an internal slot | | Swallow the value | a legitimate nonce on their own element | the nonceable check | | Retarget the element | the application's own nonced element | `base-uri` | ## What these rules do not do - They do not stop content being injected. A policy is a second line of defence, written on the assumption that the injection already happened. - Blanking the attribute does not make the nonce secret from script running in the document; it removes it from the attribute surface only. - The nonceable check inspects the element's attributes for the tell-tale fragment and the parse error; it is not a sanitizer and does not judge what the element would do. - None of the three helps if the value itself is guessable or if the same value appears on more than one response - a different subject, and one these three rules simply assume has been handled. The useful summary for an interview is that a nonce is only as good as the markup around it, and the specifications spend three separate mechanisms making sure the markup cannot lend the value out.
- If the value is still reachable from the page's own code, what has blanking the attribute actually bought?It removes the value from the attribute surface. Attribute values can be matched by a style rule and leaked through the URL that rule fetches, with no script involved. The internal slot is not an attribute, so that channel reads an empty string while the application still reads the value.
- What has base-uri got to do with nonces at all?Nothing about matching. It closes the case where the application's own nonced element keeps its nonce but its relative `src` resolves elsewhere because an injected `<base>` changed the document's base URL. `base-uri 'none'` prevents any `<base>` from taking effect.
- Does the nonceable check inspect what the element would execute?No. It looks at the element's attributes for a fragment of a start tag such as `<script` in a name or a value, and at whether the element had a duplicate-attribute parse error. It is a defence against markup the parser had to recover from, not a content inspection.
saying these in an interview costs you the question
- Thinks the nonce value is gone from the document entirely
- Says non-script readers cannot see attributes, so hiding it is pointless
- Assumes any element carrying the right string always runs
- Treats base-uri as a fetch directive constraining script sources
- Believes the nonceable check inspects what the script would do
- Thinks these rules prevent the injection rather than its payoff