Content Security Policy Nonces Control Script Execution

A Content Security Policy can turn script execution from a broad location rule into an explicit per-response decision. Instead of trusting every script served from an allowed host, the server places a fresh nonce in the policy and copies that value only onto script elements it intends to authorize.

Content-Security-Policy: script-src 'nonce-r4nd0mBase64Value'
<script nonce="r4nd0mBase64Value" src="/assets/app.js"></script>

A script element without the matching nonce is not authorized by that directive. This makes injected markup less useful to an attacker when the injection cannot obtain a valid nonce.

The nonce is a response-scoped capability

The nonce value is generated by the server for an HTTP response. It must be unpredictable and must not be reused as a stable application constant. The response header and authorized script elements carry the same value.

That design matters because a fixed value eventually becomes ordinary public markup. Once an attacker can predict or reuse it, the value no longer distinguishes server-authorized script elements from injected ones.

Nonce generation therefore belongs in request or response handling, using a cryptographically secure random source. The generated value can then be passed into the template renderer and the CSP header builder for that response.

request
  -> generate fresh nonce
  -> build CSP header
  -> render approved script tags with the same nonce
  -> send response

The nonce is not a secret after the response reaches the browser. Its security role depends on unpredictability before the response is produced and on preventing an injection primitive from placing that valid value onto attacker-controlled script markup.

Host allowlists and nonces express different trust

A host-based policy such as script-src 'self' trusts script resources based on origin. That can be useful, but it also means any script resource reachable from the trusted origin may fall inside the policy boundary.

A nonce-based policy authorizes individual script elements emitted by the application. The browser checks the nonce attached to the element against the nonce source in the policy.

This distinction is especially relevant when an origin serves user-controlled files, legacy endpoints, JSONP-like responses, or other content that should not automatically inherit script trust. A nonce does not repair those systems, but it can narrow the script execution rule.

Inline scripts can be authorized without unsafe-inline

CSP blocks ordinary inline script when a restrictive script-src applies. A matching nonce provides a targeted exception.

<script nonce="r4nd0mBase64Value">
  window.appConfig = { apiBase: "/api" };
</script>

This is materially narrower than adding 'unsafe-inline', which permits inline script broadly under the directive. Applications that require small inline bootstrap blocks can authorize those specific elements instead.

Event-handler attributes such as onclick are not converted into nonce-authorized script merely because a nearby element has a nonce. Moving executable behavior into explicit script elements also makes the authorization boundary easier to audit.

strict-dynamic can extend trust from an authorized script

A nonce can be paired with 'strict-dynamic':

Content-Security-Policy: script-src 'nonce-r4nd0mBase64Value' 'strict-dynamic'

In supporting browsers, 'strict-dynamic' allows trust granted to a nonce- or hash-authorized script to propagate to scripts that trusted script loads programmatically. This is useful for applications whose bootstrap script loads additional modules or bundles.

The effect is different from simply adding more hostnames. The trust path starts from an explicitly authorized script rather than from every resource at a listed script origin.

Compatibility requirements still matter. A production policy can contain additional source expressions for older clients, but those expressions should be chosen from the application’s actual support matrix rather than copied from a generic CSP example.

A nonce does not sanitize attacker-controlled data

CSP is a browser enforcement layer, not an input sanitizer or output encoder. Context-appropriate escaping remains necessary when untrusted data enters HTML, attributes, URLs, CSS, or JavaScript.

A dangerous pattern is placing a nonce on a script element whose body contains attacker-controlled text without safe serialization:

<script nonce="...">
  const profile = /* attacker-controlled serialization */;
</script>

The browser sees an authorized script element. CSP does not inspect the JavaScript data flow and decide whether the script body is safe. The application still owns that boundary.

The same principle applies to DOM-based injection. If trusted JavaScript converts untrusted strings into executable script or dangerous HTML, the nonce is not a substitute for safe DOM APIs and strict data handling.

Templates must keep the nonce on approved elements only

A templating layer often needs access to the nonce so it can render authorized script tags. That does not mean the nonce should be exposed as a general-purpose template variable available to arbitrary user-controlled fragments.

The rendering path should make the authorization decision explicit. Shared helpers can emit known application scripts with the nonce, while untrusted content remains data.

This separation also reduces accidental propagation. A component that can copy arbitrary attributes from user input onto a <script> element can undermine the intended boundary if it can also receive the valid nonce.

Caching requires deliberate handling

Per-response nonces interact with HTML caching. If a cache stores a rendered page containing a nonce together with its CSP header, replaying that complete response also replays the nonce.

That behavior defeats the goal of generating a fresh value for each response. Systems that cache HTML need a design that keeps header and markup values synchronized while still producing fresh nonce material, or they need a different CSP strategy such as hashes where the content model fits.

Changing only the header or only the markup is also incorrect: the values must match for the authorized script to execute.

Reporting helps expose policy breakage

A restrictive policy can block scripts that the application still depends on. CSP reporting mechanisms can provide evidence about violations during rollout, and Content-Security-Policy-Report-Only can evaluate a candidate policy without enforcing it.

Report-only mode is a deployment aid, not the final control. Once the policy matches intended application behavior, enforcement must come from the Content-Security-Policy header.

Reports can also contain URLs or other operational context, so collection endpoints and retention rules should treat them as security telemetry rather than harmless debug output.

The boundary is explicit script authorization

Nonce-based CSP works best when the application can identify the script elements it intends to execute, generate fresh nonce material for each response, and keep that value confined to those elements.

It does not replace output encoding, safe DOM construction, dependency controls, or server-side authorization. Its value is narrower and concrete: injected script markup does not gain execution merely because it appears in the document. Execution requires authorization that the server attached to the response.