Content Security Policy Nonces Authorize Individual Script Elements
Inline JavaScript creates a difficult boundary for a strict Content Security Policy. A page may need a small bootstrap block generated by the application, while arbitrary inline script must remain blocked. Allowing all inline execution with 'unsafe-inline' removes much of the value of script-src.
A nonce gives the response a narrower mechanism. The server generates an unpredictable value for that response, places it in the CSP source list, and attaches the same value only to script elements that are meant to execute.
The header and element carry the same value
A response can send a policy such as:
Content-Security-Policy: script-src 'nonce-r4nd0mBase64Value'The corresponding document can mark an approved script:
<script nonce="r4nd0mBase64Value">
window.appConfig = { mode: "production" };
</script>A supporting browser compares the element nonce with a nonce source in the active policy. A matching value makes that element eligible under the directive. An inline script without an accepted nonce remains blocked unless another source expression authorizes it.
The nonce is authorization metadata for the element. It is not a cryptographic signature over the script body, and it does not prove that the script content is benign.
A nonce must be fresh and unpredictable
A reusable nonce becomes a stable token that injected markup may be able to copy or target. The value should therefore be generated with a cryptographically secure random source and changed for each response that uses nonce-based authorization.
request A -> nonce A -> CSP A + document A
request B -> nonce B -> CSP B + document B
request C -> nonce C -> CSP C + document CThe important property is not a human-readable format. It is sufficient unpredictability for the response lifetime. Implementations commonly encode random bytes so the value is safe to place in an HTTP header and HTML attribute.
A nonce should not be derived from a timestamp, counter, username, route name, or another value that an attacker can predict. It also should not become a long-lived application secret.
Template placement is part of the security boundary
Nonce-based CSP depends on the server placing the nonce only on trusted script elements. A template pattern that copies the nonce onto every script node defeats that selection boundary.
For example, a rendering helper can receive the response nonce and use it only at explicit script sites:
<script nonce="{{ .CSPNonce }}" src="/assets/bootstrap.js"></script>The application must still protect template data from becoming markup. If attacker-controlled input can inject attributes into an already authorized script element, or alter the contents of an authorized inline script, possession of the nonce is no longer the only relevant boundary.
Nonce deployment therefore complements output encoding, safe templating, and careful DOM construction. It does not replace them.
External scripts can also carry a nonce
A nonce is not limited to inline blocks. A script element with src can carry the nonce as well:
<script nonce="r4nd0mBase64Value" src="/assets/app.js"></script>This can support policies that authorize script elements through nonces rather than broad host allowlists. The exact result still depends on the full script-src policy and browser support for the source expressions in use.
A nonce on an external script does not verify the fetched bytes. Subresource Integrity addresses a different property by checking resource content against a declared hash. The two mechanisms can appear together, but they solve different problems.
Caching must preserve header-document pairing
A response nonce appears in both the CSP header and the HTML that contains authorized elements. Caching only one side, rewriting one side independently, or combining a cached document with a newly generated header breaks that pairing.
CSP: nonce=A
HTML: nonce=A -> paired
CSP: nonce=B
HTML: nonce=A -> mismatchA cache design must treat the policy and document as one response unit. Systems that generate HTML at an origin and modify headers at a proxy need particular care because nonce generation in one layer must remain synchronized with markup generation in the other.
Static HTML also changes the tradeoff. Per-response nonces require a component capable of producing response-specific markup. A hash source may fit immutable inline script better because the policy can authorize a fixed script body without changing markup for each request.
Nonces do not sanitize dynamic JavaScript
An authorized script can still execute unsafe data if the application places unescaped input into JavaScript syntax.
<script nonce="{{ .CSPNonce }}">
const profile = {{ .SerializedProfile }};
</script>The nonce says that this script element is authorized by policy. It says nothing about whether .SerializedProfile was serialized safely for a JavaScript context.
Data crossing into script should use a serializer that produces valid, context-appropriate output. The same principle applies when an authorized script later writes into the DOM: CSP authorization does not make unsafe DOM sinks safe.
Policy scope still matters
A nonce is evaluated inside a CSP directive, so the surrounding policy determines its role. script-src may contain nonce sources alongside host sources, hashes, or other keywords. Adding a nonce does not automatically remove broader permissions already present.
For that reason, reviewing a nonce deployment requires reading the complete effective policy. A narrowly generated nonce provides little benefit if another source expression still permits script execution from locations the application did not intend to trust.
Report-only policy can help observe breakage during rollout:
Content-Security-Policy-Report-Only: script-src 'nonce-r4nd0mBase64Value'Report-only mode records policy violations where supported but does not enforce the restriction. Enforcement requires the Content-Security-Policy header.
Keep nonce generation close to response construction
The cleanest design keeps nonce generation, CSP construction, and HTML rendering in the same request path. That makes the one-response lifetime visible in code and reduces opportunities for header and markup to drift apart.
A nonce-based policy is strongest when its assumptions stay narrow: the value is unpredictable, it is fresh for each response, templates attach it only to intended script elements, and the rest of the policy does not silently reopen broad script execution. Those properties make the nonce a precise authorization token for selected script elements rather than a decorative attribute.