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:
- CSP Level 3 Browsers: The browser recognizes
'strict-dynamic'and the nonce. It enforces trust propagation, while automatically ignoringhttps:and'unsafe-inline'. Parser-inserted inline scripts without the correct nonce are blocked, while dynamically created child scripts from trusted parents are allowed. - CSP Level 2 Browsers: The browser ignores
'strict-dynamic'(which it does not recognize) and uses the nonce. It falls back to ignoring'unsafe-inline'because a nonce is present. - CSP Level 1 Browsers: The browser ignores both
'strict-dynamic'and the nonce, falling back tohttps:and'unsafe-inline'.
Core Benefits of Using
strict-dynamic
- Simplified Maintenance: Developers no longer need to maintain extensive, fragile lists of external script domains.
- Compatibility with Bundlers: Modern frameworks and bundlers (such as Webpack, Vite, or Next.js) that perform dynamic chunk loading work without custom nonce-injection plugins.
- Enhanced Security: By removing dependency on domain
allowlists,
'strict-dynamic'closes common bypass vectors such as JSONP endpoints and hosted script gadgets.