All posts

strict-dynamic explained, drop host allowlists for a strict CSP

CentralCSP Team ·

Last update:

Host allowlists in a Content Security Policy (CSP) feel safe and usually are not. You list the domains your scripts come from, but you are trusting every file on each of those domains, including ones you did not put there. One trusted CDN that also serves an attacker-reachable file, and the allowlist is bypassed. The 'strict-dynamic' keyword is the modern answer: instead of trusting domains, you trust specific scripts you mark, and let those scripts vouch for the ones they load.

The problem strict-dynamic solves

Take a policy like script-src 'self' https://cdn.example.com. It trusts every script on that CDN, and by extension every script those scripts load. Security researchers have repeatedly shown that large allowlists are bypassable: a JSONP endpoint, an open redirect, or an outdated library hosted on an allowlisted domain can be turned into arbitrary script execution that the policy happily permits. The more domains you add to keep the site working, the weaker the policy gets.

'strict-dynamic' cuts the domain out of the trust decision. The browser stops asking "is this script from an allowed host" and starts asking "was this script vouched for", which is a much harder question for an attacker to satisfy.

How it works

You mark the scripts you trust with a nonce or hash, and add 'strict-dynamic' to script-src. The browser trusts those marked scripts, and propagates that trust to any script they create at runtime, while ignoring host-source and 'self' expressions for scripts.

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

The nonce is a fresh random value your server generates per response and puts both in the header and on each trusted <script> tag (see how to set up a nonce per framework). 'strict-dynamic' then says: a script the browser already trusts (because it carried the nonce) is allowed to load further scripts, and those inherit the trust. So a trusted loader can pull in the third parties it needs without you enumerating a single host.

Trust starts at the nonce-marked script and flows outward, while host allowlists are ignored for scripts:

strict-dynamic vs a plain nonce

A nonce on its own trusts only the exact tags you put it on. That breaks the common pattern where one trusted script dynamically injects another, an analytics loader that appends a tracker, or a tag manager that writes more script tags (see GTM under a strict CSP in Next.js). Without 'strict-dynamic', those injected scripts have no nonce and are blocked. With it, they are allowed because the script that created them was trusted. That propagation is the entire reason to add the keyword on top of a nonce.

What it ignores, and what still applies

When 'strict-dynamic' is present, the browser ignores host sources, scheme sources, 'self', and 'unsafe-inline' for scripts; trust flows only from a nonce or hash. That is why you can leave a host allowlist in the policy for old browsers without weakening modern ones, the modern browser simply ignores it.

Keep object-src 'none' and base-uri 'none' in the policy regardless. 'strict-dynamic' only governs scripts, so plugins and a hijacked <base> tag are not covered, and these two directives close bypasses that script trust does not.

Browser support

'strict-dynamic' is widely supported in current browsers. Older browsers that do not understand the keyword ignore it and fall back to the host allowlist, which is why you can ship both: the allowlist is a fallback for legacy engines, not the primary control. Frame it that way rather than as something to maintain forever.

See it in your reports

Do not flip a strict policy on blind. Roll it out in Report-Only first and watch which scripts would break, because a missed dynamic loader can take out analytics or payments. CentralCSP collects those reports from real traffic, so you confirm the policy is safe against what users actually load before you enforce it.

Violations from real traffic grouped by directive and blocked origin

Next steps

Roll out a strict CSP with confidence.

Sources