A Content Security Policy can contain a long list of approved script hosts and still expose more execution authority than its author intended. A host source such as https://cdn.example.net authorizes matching script resources from that origin; it does not express which individual response or which application decision is trusted. When a permitted host serves user-controlled files, legacy JSONP endpoints, or another executable resource outside the application’s intended set, the host boundary can become too broad.

Nonce-based policies take a different route. The server places an unpredictable value in the script-src policy and on script elements it intends to authorize for that response. With the CSP Level 3 strict-dynamic source expression, trust granted to a nonce- or hash-authorized script can extend to scripts that trusted script loads dynamically. This changes the main enforcement boundary from a static host inventory to the code that receives bootstrap authority.

A nonce authorizes an element in one response

A nonce source has the form 'nonce-<base64-value>'. A matching nonce attribute on a script element allows that element to execute when the policy otherwise blocks it. The value must be unpredictable and generated separately for each HTTP response that uses it.

A simplified response can look like this:

Content-Security-Policy: script-src 'nonce-r4Nd0mBase64Value' 'strict-dynamic'; object-src 'none'; base-uri 'none'
<script nonce="r4Nd0mBase64Value" src="/assets/bootstrap.js"></script>

The security property comes from possession of the response-specific nonce, not from the string being secret after the browser receives the document. Code that can inject markup but cannot predict the nonce cannot simply add a new authorized script element in advance.

Nonce generation therefore belongs at a server-side response boundary. Reusing one fixed nonce across responses turns it into a reusable authorization token and defeats the property that makes nonce-based authorization useful. A cryptographically secure random generator is appropriate; ad hoc counters, timestamps, request IDs, and other predictable values are not substitutes.

strict-dynamic delegates script loading authority

A nonce alone can authorize the bootstrap script while leaving later script loads subject to the remaining source list. Modern applications often create script elements at runtime, so maintaining host sources for every dynamic dependency can recreate a broad allowlist.

'strict-dynamic' changes that model in user agents that implement the CSP Level 3 behavior. A script trusted through a valid nonce or hash can propagate trust to scripts it loads through non-parser-inserted script creation. The policy can therefore authorize a small bootstrap set and let that trusted code select subsequent script resources.

Conceptually, the trust chain is:

HTTP response
    |
    +-- nonce value in CSP
            |
            +-- matching bootstrap script
                    |
                    +-- programmatic script load
                            |
                            +-- programmatic script load

This is delegation, not origin approval. The dynamically loaded script does not become safe because its host appears in a list. It runs because code already holding execution authority selected it through a trust-propagating path.

That distinction also defines the principal failure mode: a trusted bootstrap script that turns attacker-controlled data into a script URL can delegate its authority to the attacker-selected resource. CSP does not inspect application intent inside that code.

Parser insertion and programmatic insertion are different boundaries

Trust propagation under strict-dynamic is tied to how a script is introduced. Scripts created by trusted code through APIs such as document.createElement('script') can participate in the dynamic trust chain. A parser-inserted script element injected as markup does not automatically inherit that trust merely because a trusted script caused some HTML to be parsed.

This distinction matters for application architecture. A loader that explicitly constructs a script element from a fixed internal module map presents a narrower authority surface than a component that accepts arbitrary HTML containing script tags.

For example, this pattern places a sensitive decision at src construction:

const script = document.createElement('script');
script.src = selectModuleUrl(moduleName);
document.head.appendChild(script);

If moduleName can be influenced by an attacker and selectModuleUrl permits arbitrary URLs, the trusted loader becomes an execution gadget. The CSP trust chain is operating as configured; the application has delegated too much authority through trusted code.

A safer design constrains the selection before the script element exists:

const modules = {
  charts: '/assets/charts.js',
  editor: '/assets/editor.js',
};

const src = modules[moduleName];
if (!src) {
  throw new Error('unsupported module');
}

const script = document.createElement('script');
script.src = src;
document.head.appendChild(script);

The policy and the loader now enforce different parts of the boundary. CSP determines which bootstrap code receives authority. Application logic determines which dynamic dependency that code may select.

Host sources become compatibility data in supporting browsers

A policy can combine a nonce, 'strict-dynamic', and host or scheme sources. In user agents that support strict-dynamic, host and scheme allowlists in the same script-src expression are ignored for script selection once the strict dynamic model applies. Older user agents that do not implement that behavior can instead use the host sources as fallback policy data.

That compatibility pattern needs careful review because two effective policies may exist across the deployed browser population. The modern path is based on nonce or hash trust plus delegation. The fallback path may still be based on origins.

Adding 'self' or CDN domains beside 'strict-dynamic' must not be interpreted as an extra restriction for supporting browsers. Those source expressions can serve the fallback case while the modern enforcement path follows the delegated trust graph.

Operationally, browser support requirements are part of the security design. If an application must support clients without the intended CSP behavior, the fallback allowlist deserves the same scrutiny as any conventional host-based policy.

Nonces do not sanitize markup

A nonce-based CSP is not an HTML sanitizer and does not make arbitrary DOM construction safe. Injection into an event-handler context, dangerous URL handling, DOM clobbering, or application-specific script gadgets can have consequences that are not reduced to adding a fresh <script> element.

The same separation applies to server templates. If attacker-controlled text can escape into a nonce attribute or into a trusted inline script body, the nonce has not repaired the templating defect. The authorization mechanism assumes the application assigns the nonce only to script elements it intends to trust and keeps untrusted data out of executable contexts.

CSP is strongest when treated as an independent enforcement layer around an application that already maintains output encoding, safe DOM construction, and narrow data-to-code boundaries.

Caching changes where nonce generation can occur

Per-response nonces interact directly with HTML caching. If a shared cache stores a complete HTML response containing both the CSP header and nonce-bearing markup, replaying that response also replays the nonce. The header and document remain internally consistent, but the value is no longer unique to a newly generated response.

A deployment can address this in several ways: generate the nonce after the shared-cache boundary, avoid shared caching for nonce-bearing HTML, or use a hash-based policy when the authorized inline bootstrap content is static and suitable for hashing. The correct choice depends on rendering and cache architecture.

What must remain invariant is the binding between the policy value and the intended script elements in the same response. Header rewriting at one layer and HTML rewriting at another can break that binding and cause either blocked scripts or accidental reuse.

Report-Only is evidence, not enforcement

Content-Security-Policy-Report-Only can expose policy violations without blocking them. It is useful during migration because existing applications often contain inline execution paths, third-party loaders, or runtime script creation that a stricter policy will surface.

Reports should be treated as telemetry. Their absence is not proof that every relevant execution path has been exercised, and a report-only policy supplies no blocking boundary. Moving to enforcement requires a policy that matches the application’s actual bootstrap and dynamic loading design.

A mature deployment can also keep reporting around the enforced policy to detect regressions and unexpected execution attempts. Report collection still needs normal operational controls: rate handling, privacy review, retention policy, and resistance to treating attacker-generated reports as trusted facts.

The bootstrap script becomes a security principal

With strict-dynamic, the most important review target is not the length of the host allowlist. It is the code that initially receives nonce or hash authorization and every path through which that code can create further script execution.

That review has concrete questions: which scripts receive the nonce, which functions can construct script URLs, which inputs reach those functions, which module maps constrain them, and which third-party loaders can extend the chain. A small bootstrap file can carry broad authority even when the CSP header itself looks minimal.

This model gives CSP a sharper boundary, but it also concentrates trust. Nonce generation must remain response-specific, nonce assignment must remain narrow, and trusted loaders must not convert untrusted strings into executable dependencies. The policy can define the delegation mechanism; the application still defines the decisions made with that delegated authority.