CSP Level 3 and Strict Dynamic JavaScript Loading

Content Security Policy (CSP) Level 3 represents the modern standard for mitigating Cross-Site Scripting (XSS) and data injection attacks on the web. This article explains the fundamentals of CSP Level 3, the limitations of legacy host-based policies, and how the introduction of the 'strict-dynamic' directive streamlines JavaScript execution by enabling trust propagation from root scripts to dynamically loaded dependencies.

What is Content Security Policy Level 3?

Content Security Policy Level 3 is the third major iteration of the W3C security standard designed to restrict the resources (such as JavaScript, CSS, images, and fonts) that a browser is allowed to load for a given page.

While CSP Level 1 and Level 2 heavily relied on domain allowlists (e.g., script-src https://trusted.cdn.com), this approach proved vulnerable in practice. Attackers often bypassed domain allowlists using open redirects, Cross-Origin Resource Sharing (CORS) misconfigurations, or hosting malicious code on legitimate CDNs. CSP Level 3 addresses these vulnerabilities by shifting focus toward cryptographic validation using random nonces (numbers used once) and cryptographic hashes, while deprecating fragile allowlist patterns.

The Challenge of Dynamic JavaScript Loading

Modern web applications depend heavily on module bundlers, package managers, and third-party script loaders (such as analytics tools and tag managers) that inject additional scripts into the Document Object Model (DOM) at runtime using APIs like document.createElement('script').

Under strict nonce-based CSP Level 2 implementations, every dynamically injected <script> tag required a matching nonce attribute. This broke many third-party libraries and tag managers, as parent scripts lacked an automated mechanism to pass their own nonce down to the child scripts they generated without extensive code refactoring.

How strict-dynamic Solves the Problem

The 'strict-dynamic' directive, introduced in CSP Level 3, modifies how the script-src directive evaluates trust. Instead of requiring an allowlist entry or individual nonce for every single script generated at runtime, 'strict-dynamic' creates a trust propagation model.

1. Trust Propagation

When a root script is explicitly trusted—typically via a cryptographic nonce (e.g., 'nonce-r4nd0m') or a hash—the browser allows that script to execute. If that trusted script creates a new <script> element and inserts it into the DOM, the browser automatically trusts and executes the newly created script as well.

2. Disabling Host Allow lists and Inline Restrictions

When 'strict-dynamic' is present in the script-src directive alongside a nonce or hash: * Host-source allowlists (such as https:, 'self', or specific domains like example.com) are completely ignored by CSP Level 3–compliant browsers. * Directives like 'unsafe-inline' are ignored.

This design prevents malicious injection into the DOM via existing allowlisted domains while maintaining full functionality for trusted script loaders.

Example Policy and Backward Compatibility

A standard CSP Level 3 implementation utilizing 'strict-dynamic' is defined as follows:

Content-Security-Policy: script-src 'nonce-r4nd0m123' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'none';

Parsing Behavior Across Browser Generations:

Core Benefits of Using strict-dynamic