CSP strict-dynamic: When to Use It and How to Migrate Safely
'strict-dynamic' lets a single nonce-trusted script bring in everything it needs, replacing brittle allowlists. Here is when to adopt it and how to migrate safely.
Modern CSPs use nonces with 'strict-dynamic' instead of host allowlists.
- Understand what 'strict-dynamic' actually delegates.
- Migrate from allowlist CSP to nonce + 'strict-dynamic'.
- Keep backwards-compatible fallbacks for older browsers.
- Validate in report-only mode before enforcement.
Using a CSP strict-dynamic directive is the modern standard for securing complex web applications. It allows a single trusted script to dynamically load other scripts, eliminating the need to maintain massive, fragile host allowlists. However, you should not use it if your application relies entirely on static, predictable script tags without dynamic loading, as a simple nonce or hash policy would suffice.
In this guide, we will explore what 'strict-dynamic' actually does, why the industry is moving away from allowlists, and how to migrate your Content Security Policy (CSP) safely without breaking production.
What 'strict-dynamic' does
The 'strict-dynamic' keyword in a Content Security Policy fundamentally changes how trust is evaluated for scripts.
The browser ignores allowlist hosts; trust propagates from the original script
When you include 'strict-dynamic' in your script-src directive, modern browsers change their behavior: they ignore traditional host-based allowlists (like https://example.com) and scheme sources (like https:). Instead, trust is established cryptographically using a CSP nonce or CSP hash sources.
Once an entry script is explicitly trusted via a nonce or hash, 'strict-dynamic' allows that script to dynamically create and execute new <script> elements. The trust “propagates” from the parent script to its children. This is particularly useful for modern JavaScript frameworks and third-party widgets (like analytics or chat tools) that load additional dependencies at runtime.
For a deep dive into the specification, you can read the W3C CSP3 ‘strict-dynamic’ spec section.
The migration story (CSP Level 2 → Level 3)
The transition from CSP Level 2 to Level 3 introduced 'strict-dynamic' to solve a critical usability problem with earlier CSP implementations.
Why allowlists stop scaling
In the early days of CSP, developers relied on host allowlists. A legacy allowlist CSP might look like this:
Content-Security-Policy: script-src 'self' https://analytics.example.com https://chat.example.com https://cdn.example.com;
This approach fails at scale. Third-party scripts often load dependencies from other domains, which change without notice. Maintaining this list becomes a full-time job, leading to broken sites or overly permissive policies that defeat the purpose of CSP. Furthermore, research has shown that host allowlists can often be bypassed if any of the allowed domains host a JSONP endpoint or an open redirect.
Backwards compatibility ('unsafe-inline' and https: fallbacks)
When migrating to a Level 3 policy with 'strict-dynamic', you must ensure older browsers don’t break. Browsers that don’t understand 'strict-dynamic' or nonces will fall back to the legacy directives. By including 'unsafe-inline' and scheme sources like https:, you provide a safety net. Modern browsers will ignore 'unsafe-inline' when a nonce is present, and they will ignore https: when 'strict-dynamic' is present.
The recommended modern CSP shape
The modern, recommended approach to CSP—often endorsed by tools like the Google CSP Evaluator—relies on a strict, nonce-based policy.
Here is the strict dynamic CSP example:
Content-Security-Policy: script-src 'nonce-rAnd0m123' 'strict-dynamic' 'unsafe-inline' https: http:;
And the matching HTML entry script would look like this:
<!-- The nonce must match the one in the CSP header -->
<script nonce="rAnd0m123" src="https://example.com/app.js"></script>
<!-- If app.js dynamically creates another script tag, it will be allowed by 'strict-dynamic' -->
This shape provides robust security in modern browsers while maintaining compatibility with older ones. For more details on this pattern, check out the web.dev / Chromium docs on ‘strict-dynamic’.
How 'strict-dynamic' interacts with nonces and hashes
'strict-dynamic' is not a standalone directive; it requires a root of trust. This trust is established using either a CSP nonce or CSP hash sources.
When a script is validated by a nonce or a hash, it is considered trusted. If 'strict-dynamic' is present, this trusted script is granted the authority to load additional scripts. However, it’s important to note the cache and nonce overlap: nonces must be uniquely generated for every single HTTP response. If you are serving static HTML from a CDN, you cannot use nonces effectively and must rely on hashes instead.
Migration playbook
Migrating from a legacy allowlist to a modern strict CSP requires careful planning. Follow this playbook to roll out 'strict-dynamic' safely.
1. Inventory script loaders
Identify all entry scripts in your application. Look for inline scripts, third-party tags, and application bundles that dynamically inject other scripts into the DOM.
2. Add nonce or hash to entry script
Modify your backend to generate a cryptographically secure, random nonce for every request. Inject this nonce into your CSP header and append it as a nonce attribute to all trusted <script> tags. Alternatively, calculate the SHA-256 hash of your inline scripts.
3. Add 'strict-dynamic' alongside the nonce
Update your script-src directive. Add the 'strict-dynamic' keyword. This tells modern browsers to allow your nonce-trusted scripts to load their dependencies automatically.
4. Keep the allowlist as fallback for legacy browsers
Do not remove your existing allowlist immediately. Keep 'unsafe-inline' and your scheme/host sources (https:, http:) in the policy. Modern browsers will drop them in favor of the nonce and 'strict-dynamic', while older browsers will rely on them.
5. Validate in Content-Security-Policy-Report-Only
Never deploy a new CSP directly to enforcement. Use report-only mode first:
Content-Security-Policy-Report-Only: script-src 'nonce-rAnd0m123' 'strict-dynamic' 'unsafe-inline' https: http:; report-uri /api/csp-report;
Monitor the reports for a few weeks to ensure no legitimate scripts are blocked before switching to enforcement.
Browser support and gotchas
Support for 'strict-dynamic' is excellent across modern browsers, including Chrome, Edge, and Firefox.
Explicit Safari note: Safari supports 'strict-dynamic' starting from version 15.4 (released in March 2022). If a significant portion of your user base relies on older Safari versions or older iOS devices, the backwards-compatible fallbacks ('unsafe-inline' and https:) are absolutely critical to prevent your site from breaking.
Another gotcha is that 'strict-dynamic' only propagates trust to scripts created via DOM APIs (like document.createElement('script')). It does not allow scripts injected via document.write() or inline event handlers (like onclick="doSomething()"). You will need to refactor these legacy patterns.
Frequently asked questions
What does 'strict-dynamic' do in CSP?
'strict-dynamic' allows a script that has been explicitly trusted (via a nonce or hash) to load additional scripts dynamically, without needing those additional scripts to be explicitly listed in the CSP.
Does 'strict-dynamic' work in Safari?
Yes, Safari supports 'strict-dynamic' starting from Safari 15.4. For older versions, you should provide fallback mechanisms like 'unsafe-inline' and host allowlists.
Can I use 'strict-dynamic' without a nonce or hash?
No, 'strict-dynamic' requires a cryptographic nonce or hash to establish the initial trust. If neither is present, modern browsers will ignore 'strict-dynamic'.
How does 'strict-dynamic' interact with report-uri?
When a script violates the policy (e.g., it wasn’t loaded by a trusted script), the browser will block it and send a violation report to the endpoint specified in your report-uri or report-to directive.
Should I keep 'unsafe-inline' when I add 'strict-dynamic'?
Yes, keeping 'unsafe-inline' alongside a nonce and 'strict-dynamic' acts as a backwards-compatible fallback. Modern browsers will ignore 'unsafe-inline' when a nonce is present, while older browsers will use it.
Rolling out a modern CSP doesn’t have to be a guessing game. By using nonces and 'strict-dynamic', you can secure your application without the maintenance nightmare of allowlists. To ensure a smooth migration, Start CSPify Starter to monitor what your new strict-dynamic CSP blocks across environments, or experiment with policies in our free CSP Builder.
Roll out 'strict-dynamic' without surprises
Start Starter to monitor what your new strict-dynamic CSP blocks across environments.