Featured image of post Why and How to Adopt 'strict-dynamic' in CSP Featured image of post Why and How to Adopt 'strict-dynamic' in CSP

Why and How to Adopt 'strict-dynamic' in CSP

Understand the benefits of the 'strict-dynamic' directive in Content Security Policy (CSP) Level 3 to trust dynamically injected script tags.

Introduction

Implementing a Content Security Policy (CSP) is a highly effective way to mitigate Cross-Site Scripting (XSS) risks. However, configuring and maintaining the policy can become a major headache.

For websites utilizing third-party SDKs (such as Google Tag Manager, analytics beacons, payment widgets, or social sharing buttons), these scripts often dynamically load (inject) additional scripts from nested domains. This forces developers to maintain a long, fragile whitelist of external domains in the script-src directive.

The 'strict-dynamic' directive, introduced in CSP Level 3, solves this problem. This article explains how it works and how to apply it.


1. The Limitations of Domain Whitelisting in CSP

In older versions of CSP (Levels 1 and 2), developers relied on whitelisting specific domains allowed to load and execute scripts:

Content-Security-Policy: script-src 'self' https://www.google-analytics.com https://cdn.jsdelivr.net;

Challenges with Whitelisting

  1. Maintenance Overhead: If a third-party SDK (e.g., google-analytics.com) updates its internal logic to fetch a helper script from a new domain (e.g., static.cloudflareinsights.com), your website will block the new script. You must update your CSP headers immediately to restore functionality.
  2. Whitelist Bypasses: If a whitelisted CDN (e.g., cdnjs.cloudflare.com) hosts an outdated library with known vulnerabilities or a script that allows arbitrary execution, an attacker can use it to bypass your CSP and execute malicious code.

2. How ‘strict-dynamic’ Works

The 'strict-dynamic' directive simplifies script trust management by establishing a trust chain.

Core Rules

  • Trust Chain Propagation: When 'strict-dynamic' is enabled, any script that is already trusted (via a cryptographic nonce or hash) is allowed to dynamically load new script tags. The browser automatically trusts the children of trusted scripts.
  • Domain Whitelist Override: When a browser detects 'strict-dynamic' in your CSP, it ignores any domain-based source expressions (like https://example.com) and 'self' within the script-src directive. Trust is determined strictly by the cryptographic nonces or hashes.

3. Implementation Example

To use 'strict-dynamic', you must pair it with either a cryptographic nonce (a random token generated per request) or a file hash.

HTTP Header Configuration

Content-Security-Policy: script-src 'nonce-RndOmValue123' 'strict-dynamic' 'unsafe-inline' https:;
  • 'nonce-RndOmValue123': Trusts any inline or external script tag containing this matching nonce attribute.
  • 'strict-dynamic': Instructs the browser to trust any child script dynamically injected by a nonce-trusted script.
  • 'unsafe-inline' https:: Fallback options for older browsers that do not support CSP Level 3. Modern browsers automatically ignore these when 'strict-dynamic' is present.

HTML Markup Example

<!-- The browser executes this script because of the matching nonce attribute -->
<script nonce="RndOmValue123">
  // Since this parent script is trusted, strict-dynamic allows it to inject child scripts
  const script = document.createElement('script');
  script.src = 'https://example.com/dynamic-library.js';
  document.head.appendChild(script);
</script>

With this setup, you do not need to add https://example.com to your CSP headers. The browser executes dynamic-library.js because it was injected by a trusted parent script.


4. Key Considerations: Parser-Injected Scripts

Be aware that some legacy libraries inject scripts using document.write('<script src="..."></script>'). These parser-injected scripts are not trusted by 'strict-dynamic' and will be blocked by the browser.

To resolve this, you must update the script loading code to use standard, modern DOM APIs like document.createElement('script').

Conclusion

Adopting the 'strict-dynamic' directive in your CSP increases security while simplifying maintenance.

  1. Eliminate the need to maintain a long whitelist of third-party domains.
  2. Allow trusted parent scripts to dynamically inject dependencies.
  3. Include fallback expressions ('unsafe-inline' https:) to support older browsers.

Consider using 'strict-dynamic' and cryptographic nonces in your project’s CSP configuration to secure your application.