CSP Nonces Bind Inline Scripts to Individual Responses

Inline JavaScript creates an awkward boundary for a strict Content Security Policy. A policy that allows every inline script with 'unsafe-inline' gives injected script blocks the same execution privilege as intended code. A nonce provides a narrower mechanism: the server generates an unpredictable value for one response, places that value in the policy, and attaches it only to script elements that are meant to run.

The browser then has two pieces to compare. A script element carrying the response’s nonce can satisfy the nonce source in script-src; an injected inline script without that value does not gain permission merely because it appears in the document.

This is an execution control, not an input-sanitization mechanism. The application still needs safe handling for untrusted data, and the nonce itself must not become attacker-controlled markup.

The nonce belongs to one response

A nonce is intended to be fresh and difficult to predict. The server generates it while producing the HTTP response and uses the same value in the CSP header and approved script elements for that response.

A minimal shape is:

Content-Security-Policy: script-src 'nonce-r4nd0mValueHere'
<script nonce="r4nd0mValueHere">
  bootApplication();
</script>

The illustrative value above is deliberately not a generation recipe. Production code should use a cryptographically secure random source and enough entropy for the deployment’s security requirements.

The important property is per-response freshness. Reusing a fixed nonce across many responses turns it into a long-lived token that can be copied once observed. A nonce is not a secret from the recipient of the page; it is a capability scoped to the document response in which the browser enforces the policy.

The server must place it selectively

Nonce generation is only half of the design. The template layer must add the nonce to scripts that the application intends to authorize, without automatically adding it to attacker-influenced script elements.

Consider a template that emits a trusted bootstrap block:

<script nonce="{{ csp_nonce }}">
  window.appConfig = {{ trusted_config_json }};
</script>

The nonce attribute is safe only if csp_nonce comes from server-side response state and the surrounding template preserves attribute boundaries. The configuration value also needs encoding suitable for its JavaScript context. CSP does not make unsafe interpolation safe.

A dangerous abstraction is one that scans generated markup and adds the current nonce to every <script> element. If an injection flaw can create a script element before that transformation, the application may authorize the injected element itself. Authorization should follow the provenance of trusted template code rather than the tag name alone.

Nonces change the role of inline code

Without a nonce or hash source, a restrictive script-src normally blocks inline script blocks and inline event handlers. A nonce source gives selected script elements an explicit route through that restriction.

This makes migration practical for applications that still require some server-rendered inline bootstrap code. Instead of enabling 'unsafe-inline' for all inline script, the application can mark a small set of intended blocks.

The policy can remain explicit:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-<response-value>';
  object-src 'none';
  base-uri 'none'

Actual directives should match the application’s resource model. Copying a sample policy without inventorying scripts, workers, frames, connections, styles, and other resource types can break required behavior or leave unrelated policy gaps.

A nonce does not sanitize a script body

A nonced script is trusted by the policy. If untrusted text is inserted into that script body in a way that changes JavaScript syntax, the browser can execute the resulting code because the containing script already carries an accepted nonce.

For example, constructing JavaScript source by concatenating raw user input remains hazardous:

<script nonce="{{ csp_nonce }}">
  const displayName = "UNTRUSTED_VALUE";
</script>

The safe representation depends on the data flow and framework. Structured data serialization, context-correct encoding, or moving data into a non-executable representation can keep data from becoming program text. The relevant point is that nonce authorization applies to the script element; it does not inspect whether every byte inside that element came from a trusted source.

Nonce exposure is different from nonce injection

A page recipient can inspect its own document and see the nonce. That fact does not by itself defeat the control. An attacker exploiting an injection flaw remotely still needs a path that results in executable markup carrying an accepted nonce in the victim’s document.

The more serious design error is allowing untrusted input to populate a nonce attribute, copy a nonce into attacker-created executable markup, or enter a trusted script block as executable source. Those paths collapse the distinction the policy is meant to enforce.

For the same reason, server-side rendering helpers should treat the nonce as response metadata with a narrow API. Passing it through general application data structures or exposing it to unrelated template fragments increases the number of places that can accidentally authorize code.

Caching needs deliberate treatment

A per-response nonce interacts with HTML caching. If a cache stores a complete response containing both the CSP header and nonced markup, replaying that response also replays the nonce.

Whether a cached response is acceptable depends on the architecture, but a design that requires a fresh nonce for each delivered response cannot simply cache the final HTML and header indefinitely. Common designs generate or substitute the nonce at a layer that runs for each response, or avoid caching the final personalized document.

Header and body values must also stay synchronized. Rewriting only the header or only the markup causes legitimate scripts to fail the policy check.

Report-only mode helps migration, not enforcement

Content-Security-Policy-Report-Only can expose policy violations without blocking them. It is useful while inventorying script behavior and identifying code that still depends on broad allowances.

Report-only mode is not a protection boundary. A policy must be delivered as Content-Security-Policy for the browser to enforce its restrictions. Reports can also contain noisy or incomplete signals, so operational use benefits from filtering and correlation rather than treating every event as a confirmed attack.

A staged rollout can move from observation to enforcement after required execution paths have explicit authorization and unexpected dependencies have been removed.

Nonces fit inside a larger script policy

Nonce-based authorization is strongest when the rest of the script policy does not reopen broad execution paths. Adding a nonce while retaining permissive sources can leave alternate routes for script execution.

The surrounding application also matters. DOM injection sinks, unsafe HTML construction, vulnerable dependencies, and server-side template flaws remain relevant even with CSP deployed. CSP is a browser-enforced mitigation layer that can constrain the consequences of some injection paths; it is not a replacement for fixing those paths.

A disciplined nonce design keeps the boundary small: generate a fresh value for the response, place it in the enforced policy, attach it only to intended script elements, preserve safe data encoding inside those elements, and keep cache behavior consistent with nonce freshness. That gives inline code an explicit execution grant without granting the same privilege to every inline script that reaches the document.