Safety
CSP strict-dynamic vs Nonce Host Allowlists
March 20, 2023
CSP strict-dynamic vs nonce host allowlists is two ways to trust scripts after you already have a nonce or hash: the nonce marks the tags you meant to run, a host list allows every file on that origin, and 'strict-dynamic' tells supporting browsers to ignore those hosts and let a trusted script load children it creates. That eases tag managers; it also means a trusted script that builds tags from attacker-controlled URLs is now a loader. RoastMyOpsec reads CSP quality signals (missing policy, unsafe-eval, unqualified unsafe-inline) — it does not score strict-dynamic alone and does not execute XSS.
The practical difference
A nonce allowlist without strict-dynamic is explicit: every script element you want must carry this response's nonce (or a matching hash). Extra <script src> injected by a trusted loader is blocked unless you also listed that host — which is how CDNs become a second XSS origin.
strict-dynamic, with a nonce or hash root of trust, drops host and scheme allowlists for script-src in browsers that understand it. Older browsers ignore the keyword; that is why compatibility snippets still add 'unsafe-inline' and https: behind it. Those fallbacks are ignored once strict-dynamic is understood. A static nonce on cached HTML still ruins either design.
How to choose based on what you shipped
SSR or edge HTML you control: nonce per response, no unqualified unsafe-inline, then decide whether loaders must spawn children. If GTM or a bundler injects scripts, strict-dynamic is the usual way to keep the nonce model without listing every third-party host. If you have a tiny set of first-party files and no runtime loaders, skip strict-dynamic and list those origins or use hashes plus SRI.
Do not add strict-dynamic to a host-only policy with no nonce or hash — supporting browsers will ignore the hosts and have nothing to trust. Do not treat strict-dynamic as a reason to skip frame-ancestors.
| Option | When it wins | Watch-out | Takeaway |
|---|---|---|---|
| Nonce (or hash) only | You mark every script; no runtime loaders | A child <script> from a loader is blocked | Tightest if the HTML is fully in your templates |
| Nonce + strict-dynamic | Trusted loaders must fetch more scripts | Those loaders become transitive trust | Usual strict-CSP pattern for apps with widgets |
| Host allowlist | Legacy CDNs you cannot nonce | Anything hosted on a listed origin can run | Weaker; pair with SRI, plan to graduate |
| unsafe-inline without nonce/hash | Never as a production plan | Inline XSS is in policy | Report-Only until you can drop it |
When a host allowlist still wins
A host allowlist still wins as a temporary belt on static exports where you cannot rotate nonces, or on old browsers while you keep a nonce+strict-dynamic policy in front. It does not win as the long-term XSS story if that host also serves a compromised widget.
strict-dynamic wins when a nonce'd bootstrap must load children you do not want to enumerate. Hashes win for tiny immutable inline. Neither keyword is scored by name on a RoastMyOpsec header pass.
Common mistakes
The first mistake is 'strict-dynamic' with no nonce or hash, which leaves modern browsers with an empty script-src trust root.
The second mistake is a CDN-cached HTML file with a frozen nonce.
The third mistake is assuming a roast 'CSP present' grade means strict-dynamic was reviewed.
What a URL roast can prove
The free header pack looks for Content-Security-Policy, flags unsafe-eval, and flags unsafe-inline when no nonce or sha256/384/512 token is in the policy. Presence of 'strict-dynamic' is not a pass and absence is not an F. Framing is a separate check (frame-ancestors or X-Frame-Options). The roast does not hash your inline scripts, does not prove the nonce rotates, does not score GPC, and does not submit HSTS preload. Fix CSP at the edge, then re-scan the same URL.
Free audit the URL you own
RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.
Free audit nowFAQ
- Do I need strict-dynamic if I already use nonces?
- Only if trusted scripts must load additional scripts without those tags carrying the nonce. If every script is in your HTML with the nonce, you can omit strict-dynamic.
- Does RoastMyOpsec require strict-dynamic?
- No. It does not score strict-dynamic alone. It grades CSP presence and a few quality signals such as unqualified unsafe-inline and unsafe-eval.
- Can host allowlists replace nonces?
- They allow anything on that origin, including a future XSS on a listed CDN. Prefer nonces or hashes for script-src; keep hosts only as a compatibility layer you are retiring.