A script policy built around a long list of approved hosts can look restrictive while still granting more authority than the application intends. If any approved origin can serve attacker-influenced JavaScript, or exposes a path that behaves as a script gadget, the hostname boundary may admit code that the page never meant to execute.

A strict Content Security Policy changes the basis of that decision. Instead of treating network location as the primary proof that a script is acceptable, the page marks specific root scripts with a fresh nonce or a matching cryptographic hash. With 'strict-dynamic', trust can then follow script-loading relationships created by those authorized roots.

That shift matters because the security boundary becomes tied to code selected by the document producer rather than every script-capable response available beneath an approved host.

Host allowlists grant authority at origin scale

A conventional script-src policy may name the application origin plus several content delivery or vendor hosts:

Content-Security-Policy: script-src 'self' https://cdn.example.net https://vendor.example

The browser evaluates script requests against those source expressions. A matching location can satisfy the policy even when the application did not intend that particular resource to become executable code.

This creates a structural problem for large origins. A hostname is often shared by many paths, applications, upload systems, redirects, legacy endpoints, or third-party assets. CSP source expressions can narrow network locations, but maintaining a precise URL-oriented allowlist across changing application dependencies is difficult. A policy can remain syntactically valid while its practical trust surface expands.

The issue is not that approved hosts are inherently unsafe. The issue is granularity: host authorization and script authorization are different decisions.

A nonce binds execution to a generated response

A nonce-based policy places an unpredictable value in the response header and on script elements selected by the server:

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

The illustrative value above is intentionally not suitable for deployment. A production nonce must be generated with a cryptographically secure source and must be fresh for each response.

The browser compares the nonce source in the policy with the nonce attached to the script element. A matching nonce authorizes that root script. Injected markup without the response’s nonce does not gain the same authorization merely because it points at the application origin or another familiar host.

This property depends on keeping the nonce outside attacker control. Reusing a predictable value, exposing it through an injection primitive that can copy it into attacker-created script markup, or placing it on scripts that consume attacker-controlled data as code can collapse the intended boundary.

A nonce therefore marks trust; it does not sanitize JavaScript or repair an unsafe script gadget.

Hashes anchor trust to exact script bytes

A hash source provides a different root of trust. The policy contains a supported digest such as SHA-256, SHA-384, or SHA-512 for the authorized script content. For inline script, the browser computes the digest over the script text and compares it with the policy.

This model fits content whose bytes remain stable. Any relevant content change requires a corresponding hash update. That maintenance cost is also the security property: authorization follows the selected bytes rather than a reusable host identity.

Nonce-based policies fit dynamically rendered responses more naturally because the server can generate a fresh token and attach it to selected script elements. Hash-based policies can fit static delivery pipelines where script content and policy metadata are produced together.

Neither mechanism makes arbitrary script execution safe. Both provide a narrower authorization primitive for deciding which scripts begin with CSP trust.

strict-dynamic propagates trust through script loading

Modern JavaScript applications frequently load code after the initial HTML parse. A trusted bootstrap script may create a new <script> element, assign its src, and insert it into the document. Requiring every dynamically created script element to carry a server-generated nonce can conflict with loaders that operate entirely in JavaScript.

The 'strict-dynamic' source expression addresses that model. When a script is trusted through a nonce or hash, trust can extend to scripts that it loads dynamically. The browser evaluates the loading relationship rather than requiring the child script to match a traditional host allowlist.

Conceptually:

HTML
 |
 +-- nonce-authorized bootstrap
       |
       +-- dynamically inserted script
              |
              +-- dynamically inserted script

The propagation is powerful because it reduces dependence on broad hostname lists. It is also a deliberate transfer of authority. A trusted root that constructs script URLs from attacker-controlled input can authorize code that a host-based policy might otherwise have blocked.

For that reason, 'strict-dynamic' makes the behavior of trusted loaders part of the security boundary. The policy can narrow the set of roots while increasing the significance of each root’s script-loading logic.

Parser-inserted markup does not inherit the chain automatically

Trust propagation is not a general rule that every script appearing after a trusted script becomes acceptable. The relevant distinction is how the script is introduced.

A nonce- or hash-authorized root can propagate trust to scripts it creates through non-parser-inserted loading patterns, such as constructing a script element and appending it to the document. An attacker who injects an ordinary parser-inserted <script src="..."> element does not gain authorization simply because a trusted root also exists on the page.

This distinction keeps 'strict-dynamic' from becoming a blanket permission for later markup. The trusted execution chain is tied to script behavior, not document order.

Applications that use unusual loaders, HTML-writing APIs, framework-specific injection mechanisms, or legacy browser behavior still need compatibility testing. CSP enforcement is performed by the user agent, and deployment assumptions should match the browser population the application actually supports.

Source expressions change meaning under a strict policy

When 'strict-dynamic' is active with a nonce or hash in a supporting user agent, host-source and scheme-source expressions in the same script directive are ignored for script loading, along with source expressions such as 'self' and 'unsafe-inline'.

That behavior is central to the model. A policy such as:

Content-Security-Policy: script-src 'nonce-r4Nd0mV4lu3' 'strict-dynamic' 'self' https:

does not mean that a modern supporting browser accepts every HTTPS script in addition to the nonce-authorized trust chain. The legacy expressions can serve compatibility purposes for older CSP implementations, while the strict policy controls capable browsers.

Compatibility layering requires care. Older user agents can interpret the same header with materially broader semantics. A deployment that includes fallback expressions should treat those fallback paths as part of its supported security posture rather than assuming every client receives identical enforcement.

Script trust is separate from data trust

CSP is often described as a mitigation for script injection, but a strict policy does not make trusted JavaScript immune to unsafe data flow.

Consider a nonce-authorized bootstrap that reads a query parameter and turns it directly into a script URL:

const source = new URL(location.href).searchParams.get("module");
const script = document.createElement("script");
script.src = source;
document.head.appendChild(script);

Under a policy that propagates trust with 'strict-dynamic', this loader can transfer its authority to a location selected through attacker-controlled input. The browser is following the trust relationship the policy requested.

The defect is therefore inside the trusted root’s behavior. The loader must constrain script destinations using application logic appropriate to its design, or avoid converting untrusted data into executable resource selection at all.

This is a useful boundary for security review: CSP decides which execution roots receive browser trust, while application code still decides what those roots do with data.

object-src and base-uri close adjacent paths

Strict script policies are commonly paired with restrictive directives for other execution or URL-resolution surfaces. object-src 'none' blocks object, embed, and applet resources governed by that directive. base-uri 'none' prevents the document from setting a base URL through a <base> element.

The latter can matter when relative script URLs are present. If injected markup can alter the document base, a relative URL can resolve somewhere other than the application expected. Restricting base-uri removes that redirection mechanism from the document.

These directives do not substitute for a strict script-src. They cover adjacent browser behaviors that can otherwise weaken assumptions around resource selection.

Reporting exposes policy mismatches before enforcement

A strict policy can break legitimate application behavior if existing scripts depend on inline execution, string evaluation, undocumented third-party loaders, or dynamically inserted resources that do not fit the intended trust graph.

CSP reporting provides evidence about those mismatches. A report-only deployment can collect violations without enforcing the candidate policy, giving operators a view of script paths that would be blocked. Reports still require interpretation: browser extensions, injected software, stale clients, and unrelated page behavior can add noise.

The useful output is not a count of violations by itself. It is a map of execution behavior that differs from the proposed boundary. That map can reveal hidden loaders, obsolete inline handlers, unexpected third-party dependencies, and pages whose rendering path differs from the assumed architecture.

Moving from report-only mode to enforcement should follow evidence that legitimate execution roots are represented correctly and that fallback behavior matches the application’s browser support commitments.

The trust graph becomes the review surface

A strict CSP reduces the value of asking whether a script URL belongs to a familiar hostname. The more relevant security questions concern which roots receive a nonce or hash, what code those roots execute, and which descendants they can introduce.

That produces a smaller but more consequential review surface. Template logic that attaches nonces, build logic that emits hashes, bootstrap loaders, dynamic import mechanisms, and any data that influences script creation all sit close to the browser’s execution boundary.

A well-formed policy cannot compensate for granting trust to an unsafe root. Its value comes from making that grant explicit and limiting the number of places where executable authority begins.

With nonce- or hash-authorized roots and carefully reviewed trust propagation, CSP can express script authority at the level of selected code and loading relationships rather than entire hosting domains. The resulting boundary is narrower, but its strength still depends on the behavior of every root allowed to carry that authority.