Content Security Policy can restrict script execution without maintaining a long list of trusted hostnames. A nonce-based policy gives selected script elements an unpredictable, response-specific token. When strict-dynamic is also present, a supporting browser can propagate trust from those authorized root scripts to scripts they create programmatically.

This changes the security boundary. Trust is attached to an authorized execution root rather than every network origin that might serve JavaScript.

A nonce authorizes a specific script element

A server can emit a policy such as:

Content-Security-Policy: script-src 'nonce-r4nd0mValue' 'strict-dynamic'; object-src 'none'; base-uri 'none'

and place the same nonce on an intended script element:

<script nonce="r4nd0mValue" src="/assets/app.js"></script>

The nonce value must be generated so an attacker cannot predict it, and it must be fresh for each response. It belongs in the CSP header and on script elements selected by the application. Reusing a fixed nonce turns it into a reusable authorization token and defeats the property the mechanism depends on.

The browser compares the element’s nonce with the nonce source in script-src. A matching value authorizes that script under the policy. Markup injected without the current value does not gain that authorization merely because it appears in the document.

A nonce is not an escaping mechanism. Untrusted data still requires context-appropriate output handling. CSP is an additional enforcement layer, not a replacement for preventing injection.

strict-dynamic propagates trust from an authorized root

Modern applications often use a small bootstrap script that loads modules or bundles at runtime. A hostname allowlist can become broad when every possible script delivery origin must appear in script-src.

With strict-dynamic, a nonce- or hash-authorized script can create another script element and load it without requiring that descendant script’s origin to appear in a source allowlist. Conceptually:

HTML response
  -> nonce-authorized bootstrap
       -> programmatically created script
            -> further trusted descendant

The propagation is tied to scripts loaded by trusted script execution. It does not mean that every script element later present in the DOM becomes trusted. Parser-inserted scripts do not receive trust solely from strict-dynamic.

That distinction keeps the trust graph narrow: an attacker who can inject ordinary <script> markup still lacks the response nonce and does not acquire trust propagation from the directive itself.

Host allowlists solve a different problem

A source expression such as:

https://cdn.example.net

trusts scripts based on where they are fetched. That boundary can be larger than intended when a permitted origin hosts user-controlled paths, JSONP-like endpoints, legacy content, or other resources that can be interpreted as script in the relevant browser context.

A nonce plus strict-dynamic instead starts from scripts explicitly authorized by the response. This can reduce dependence on enumerating script hosts, but it also places more responsibility on the authorized roots. If a trusted root can be induced to load an attacker-chosen script URL, trust propagation can authorize that load. The loader’s input validation and URL construction therefore remain security-sensitive.

CSP does not certify the behavior of an authorized script. Once a script is trusted to execute, policy design must account for what that script can create and load.

Compatibility can be layered into one policy

strict-dynamic has defined interaction with source lists in supporting browsers. When a script-src contains a nonce or hash together with strict-dynamic, source expressions such as 'self' and host sources are ignored for script loading in browsers that implement the directive’s trust propagation rules.

Policies can include fallback source expressions for older implementations while newer implementations rely on nonce or hash roots. Such compatibility design needs testing against the browser population an application actually supports. A fallback allowlist may provide a broader boundary in clients that do not enforce strict-dynamic.

The effective protection is therefore client-dependent when compatibility sources are present. Policy text should not be treated as if every browser evaluates every directive identically.

Nonce delivery affects caching and rendering

Because the nonce must vary per response, it usually has to be inserted into both the CSP header and the corresponding HTML during response generation. Full-page caches need a design that preserves that per-response property. Serving cached HTML with a long-lived nonce creates reuse; changing only the header while leaving a stale HTML nonce causes intended scripts to fail authorization.

Template boundaries matter as well. A safe pattern keeps nonce assignment under server-controlled rendering logic rather than exposing the nonce as a value that arbitrary untrusted markup can copy.

The nonce does not need to be secret after the response reaches the browser in the same sense as a password. Its security role is to be unpredictable before the protected response is generated, so injected content cannot supply the matching authorization value in advance.

Report-Only supports policy rollout without enforcement

Content-Security-Policy-Report-Only can expose violations while a policy is being evaluated, but it does not enforce the restrictions. It is useful for identifying scripts that would be blocked before switching to an enforcing Content-Security-Policy header.

Reports are diagnostic signals, not proof that a policy is complete. Coverage depends on client behavior, reporting configuration, network delivery, and the code paths exercised during observation.

The trusted root remains part of the attack surface

A nonce-based strict-dynamic policy can make script authorization more precise than a large host allowlist, but it does not make authorized JavaScript harmless. DOM injection sinks, unsafe URL handling, compromised dependencies, and dangerous behavior inside trusted roots remain relevant.

The useful boundary is explicit: the response chooses initial script roots with unpredictable nonces or approved hashes, and strict-dynamic permits those roots to extend script trust through programmatic loading. Security then depends on keeping the initial authorization narrow and treating every trusted loader as code that can expand that trust graph.