CSP Strict Dynamic Moves Script Trust From Host Lists to Nonce-Bearing Roots

A production page can have a restrictive script-src policy and still depend on a bootstrap script that creates additional script elements at runtime. A host allowlist handles that architecture by naming every permitted script origin. The list then becomes coupled to deployment topology: moving a dependency to another host can require a policy change, while admitting a broad host can authorize more executable content than the application intended.

Content Security Policy Level 3 provides a different trust model through the 'strict-dynamic' source expression. When a script is authorized by a valid nonce or hash, trust can propagate to scripts that it loads through non-parser-inserted script elements. In supporting user agents, host-source and scheme-source expressions in that script policy stop controlling those delegated loads.

The security boundary moves accordingly. The central question is no longer only which network locations may serve JavaScript. It becomes which initial script elements receive cryptographic authorization and which code those trusted roots can cause the browser to execute.

A nonce authorizes an element, not a server

A CSP nonce is a per-response value placed both in the policy and on script elements that the response author intends to execute. For a policy such as:

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

a matching script element can pass the nonce check:

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

The relevant trust decision is attached to that script element. The nonce does not state that every script under /assets/ is trusted, nor does it authorize every resource from the page’s origin. A fresh, unpredictable nonce is normally generated for each response and inserted into the policy and intended script elements.

This granularity avoids a common weakness of host-oriented policies. If a permitted origin serves many files, contains user-controlled paths, or exposes a JSONP-style execution surface, admitting the origin as a script source can authorize content beyond the specific application dependency. A nonce-bearing root narrows initial execution to markup carrying the matching value.

That benefit depends on nonce placement remaining controlled. If an injection flaw lets hostile markup acquire the valid nonce, CSP cannot distinguish that hostile script element from an intended one on the basis of the nonce alone.

Strict dynamic delegates through script-created elements

The 'strict-dynamic' expression changes what happens after an initial script has been authorized by a nonce or hash. CSP Level 3 defines trust propagation for scripts loaded through non-parser-inserted script elements created by trusted script.

A bootstrap loader can therefore create a script node, assign its src, and insert it into the document. In a user agent applying the strict-dynamic semantics, that descendant script can be admitted because its loading chain originates from a trusted script, even when the descendant’s host is absent from the policy.

This is not a general inheritance rule for all resource types. A trusted script does not gain authority to bypass img-src, connect-src, frame-src, or other directives merely because strict-dynamic appears in script-src. The propagation mechanism is specific to script execution under the applicable CSP processing rules.

Parser insertion is also a material boundary. A script element present in parsed markup does not become trusted merely because a nonce-bearing script happens to run earlier. Strict-dynamic is designed around script-initiated loading rather than blanket trust for later markup.

The resulting model resembles an execution graph. A small set of nonce- or hash-authorized roots can create trusted descendants. Security review must therefore follow the authority of those roots, including loader behavior, dependency resolution, URL construction, and any attacker-influenced data that reaches script creation.

Host allowlists lose authority in supporting browsers

A compatibility policy may contain both nonce-based strict CSP expressions and traditional sources:

script-src 'nonce-r4nd0mValue' 'strict-dynamic' https: 'self'

In CSP Level 3 processing that recognizes 'strict-dynamic' alongside a nonce or hash, host-source and scheme-source expressions such as https: and 'self' are ignored for script loading governed by that directive. They can remain useful as fallback material for older CSP implementations that do not apply strict-dynamic semantics.

This creates an important operational distinction. The visible policy string may contain familiar hosts, yet those hosts are not necessarily part of the effective authorization decision in a modern browser. Reviewing the header as a flat allowlist can therefore misstate the actual trust boundary.

Compatibility additions also deserve care. A fallback source intended for an older user agent can be broader than the modern policy. That may be an accepted compatibility trade-off, but it means the protection level is partly browser-version dependent. Policy assessment has to consider the processing model of the deployed client population rather than only the most recent CSP grammar.

Delegated trust magnifies loader authority

Strict-dynamic reduces the maintenance burden of enumerating recursive script hosts, but it deliberately grants trusted scripts substantial authority over subsequent script loading. That is useful for module loaders, tag bootstraps, and applications whose dependency locations change independently of page templates.

The same property changes failure consequences. If a trusted bootstrap script accepts an attacker-controlled URL and inserts it as a script source, the browser can treat the resulting load as part of the trusted chain. A host allowlist might have blocked that destination; strict-dynamic is intentionally not using that host list as the controlling boundary for the delegated load.

The policy therefore assumes that nonce-bearing or hash-authorized roots deserve delegation authority. Input validation inside those roots, dependency integrity, supply-chain controls, and the absence of dangerous script-loader gadgets become more significant. CSP cannot make a trusted script’s own loading decisions safe after granting that script the ability to extend trust.

This does not make strict-dynamic equivalent to unrestricted script execution. An attacker still needs a path into the trusted execution graph. Arbitrary parser-inserted script without a valid nonce or hash remains outside the authorized roots, and ordinary host expressions do not restore access in supporting browsers once strict-dynamic governs the directive.

Nonce secrecy is not the complete property

Nonce guidance is sometimes reduced to keeping a random value secret. Unpredictability is necessary, but the stronger requirement is that hostile content cannot obtain an authorization-bearing placement that the browser accepts.

The nonce appears in the delivered document because the browser needs it to evaluate script elements. The design does not depend on treating the value as a long-lived credential hidden from all code executing in the page. Instead, it prevents an attacker who can inject markup but cannot control an authorized script element from simply guessing the per-response token.

Server-side templating can break that assumption if it reflects the nonce into attacker-controlled markup or applies it indiscriminately to every script element. DOM code can create a similar problem if a trusted script copies nonce-bearing elements or turns untrusted data into executable descendants.

Nonce reuse across unrelated responses also expands the period in which one observed value could remain useful. The intended pattern is a fresh, unpredictable nonce per response, coupled only to script elements selected by the application.

Hash roots trade response state for content stability

CSP can establish trusted roots with hashes as well as nonces. A hash source binds authorization to script content rather than a per-response random value. That can suit static inline bootstrap code whose bytes are stable and whose deployment pipeline can keep the policy hash synchronized.

The operational trade-off differs. Nonces require response-specific policy and markup coordination but tolerate script content changes without recomputing a digest. Hashes avoid nonce generation for stable content but make authorization sensitive to the exact hashed bytes.

With strict-dynamic, either form can anchor delegated script loading when the applicable CSP requirements are met. The security review still centers on the root’s authority: a correctly hashed loader that constructs script URLs from hostile input can extend trust in an unsafe direction.

Reporting exposes policy violations, not policy intent

CSP reporting can surface blocked script loads and help identify compatibility failures during deployment. Report-only policy can also expose effects before enforcement is activated. These mechanisms are operationally valuable when moving from host-based policy to nonce- or hash-rooted policy.

Reports do not establish that an allowed execution chain is safe. A descendant script admitted through strict-dynamic is behaving according to policy, so its admission is not itself a violation to report. If the trusted loader made a poor security decision, CSP may have no violation event to signal.

This separates enforcement telemetry from application trust analysis. Violation reports are evidence about actions CSP rejected. They are not an audit log of every delegated trust decision and cannot substitute for review of nonce assignment or loader behavior.

The boundary is the trusted script graph

Host allowlists express script trust as network location. Strict-dynamic permits a narrower initial root and a broader transitive execution relationship. That can remove brittle dependency-host enumeration and reduce exposure to unrelated executable content on an allowed origin.

The trade-off is concentrated authority. A nonce-bearing bootstrap is not merely one permitted file among many; it can become a root from which further script trust flows. Its data inputs, dependency resolver, DOM insertion paths, and supply chain sit directly on the browser’s script-execution boundary.

A strict CSP therefore gains its security value from careful root selection rather than from the presence of a particular keyword. Nonces or hashes constrain initial execution, strict-dynamic carries that trust through deliberate dynamic loading, and the application remains responsible for keeping hostile data from steering the trusted graph into unintended code.