CSP Nonces Bind Script Execution to Each Response
Content Security Policy can restrict which scripts a browser executes after receiving a document. A nonce-based policy moves that decision away from a broad host allowlist: the server places a fresh unpredictable value in the response policy and copies that value only onto script elements it intends to authorize.
The mechanism is narrow. A nonce does not sanitize HTML, prove that a script is benign, or repair an unsafe DOM API. It gives the browser an authorization token for selected script elements in one document response.
The policy and element must carry the same value
A response can send a policy such as:
Content-Security-Policy: script-src 'nonce-RANDOM_VALUE'; object-src 'none'; base-uri 'none'The document then marks an authorized script with the matching value:
<script nonce="RANDOM_VALUE" src="/assets/app.js"></script>The browser compares the nonce source in script-src with the element’s nonce attribute. A matching nonce permits that script under the policy. A script element injected without the matching value does not gain permission merely because its URL is same-origin.
The nonce is policy data, not a secret credential that authenticates a user. Its security role depends on an attacker being unable to predict the value before the response is produced and unable to place that value onto attacker-controlled script markup through the application’s rendering path.
Generate a fresh unpredictable nonce per response
A nonce should be generated independently for each HTML response. Reusing one value across many responses changes a per-response authorization mechanism into a stable token that can be copied into later injection attempts.
The server needs a cryptographically strong random source and an encoding that is valid in the CSP source expression and HTML attribute. The same generated value is used in the response header and on every script element intentionally authorized for that document.
request
|
v
generate fresh nonce R
|
+--> response header: 'nonce-R'
|
+--> trusted script tag: nonce="R"Caching has to preserve this invariant. A cache that stores nonce-bearing HTML and serves the same rendered document repeatedly also repeats its nonce. Applications using shared or edge caches need a response design that keeps nonce generation and document delivery aligned.
Nonce insertion belongs in trusted rendering code
A templating layer should attach the nonce only to script elements selected by trusted application code. Blindly adding the nonce to every <script> element in rendered output can authorize markup that arrived through an injection flaw.
This distinction matters when a template renders untrusted HTML fragments. The renderer must not treat every script-shaped node as trusted merely because it appears before the final response is serialized.
The same concern applies to string interpolation inside an authorized inline script:
<script nonce="RANDOM_VALUE">
const profile = {{ unescaped_user_data }};
</script>The nonce authorizes the script element. It does not make unsafe interpolation safe. Data embedded into JavaScript still requires a context-appropriate serialization strategy that cannot break out into executable syntax.
strict-dynamic changes how script trust propagates
A nonce can be combined with the CSP strict-dynamic source expression:
Content-Security-Policy: script-src 'nonce-RANDOM_VALUE' 'strict-dynamic'; object-src 'none'; base-uri 'none'In supporting browsers, trust granted to a nonce- or hash-authorized root script can extend to scripts that the trusted script loads dynamically. Host source expressions, scheme source expressions, 'self', and 'unsafe-inline' are ignored for that directive when strict-dynamic applies.
This is useful for applications whose trusted bootstrap script loads additional modules or bundles at runtime. It also shifts attention to the bootstrap code. If trusted code creates script elements from attacker-controlled URLs, propagated trust can permit those loads. The policy does not validate application data flow.
A strict CSP remains a defense layer, not an input validator
Nonce-based CSP is commonly deployed as an XSS mitigation. It can block injected script elements that lack authorization even when hostile markup reaches the document.
The application still needs context-sensitive output encoding, safe DOM construction, and sanitization where HTML is intentionally accepted. These controls address different points in the path from untrusted data to executable code.
Inline event handlers and javascript: URLs are not authorized merely by placing a nonce on some other script element. APIs that evaluate strings as code also remain restricted unless the policy explicitly permits the relevant capability.
A policy that adds 'unsafe-inline' broadly can undermine the intended restriction in user agents where that source expression is effective. Policy composition therefore deserves the same review as the code that emits nonce attributes.
Report-only deployment can expose breakage before enforcement
Applications with existing inline scripts, third-party loaders, or runtime script creation can encounter blocked execution when a strict policy is enabled. Content-Security-Policy-Report-Only can be used to observe violations without enforcing the candidate policy.
Reports are diagnostic signals, not proof that a policy is complete. Test coverage should include routes, authentication states, error pages, localized templates, and dynamically loaded code paths that produce different document shapes.
Once enforcement is active, telemetry can still reveal unexpected policy violations. Reports may contain application details, so collection and retention need the same privacy and access controls applied to other operational logs.
The boundary is precise
A CSP nonce answers one browser-side question: which script elements in this response received authorization from the server’s policy. Fresh generation keeps that authorization scoped to the response, and disciplined template insertion keeps it attached to intended scripts.
That boundary is valuable because it is limited. The nonce does not certify script behavior or replace safe handling of untrusted data. It constrains execution while the application retains responsibility for the data flows that create the document and the code that trusted scripts load.
References
- MDN, Content Security Policy (CSP): https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
- MDN, Content-Security-Policy header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy