CSP: Using Trusted Types Policies for Enhanced Web Security


Modern web applications must guard against cross-site scripting (XSS) and similar injection attacks. Trusted Types is a browser feature designed to enforce strict handling of sensitive data—such as script URLs and HTML—by only allowing values that have been vetted by developer-defined policies. In this article, we explore how Trusted Types policies work, address common questions about policy creation and reuse, and examine potential risks—like global variable overrides—and how to mitigate them.
1. What Is a Trusted Types Policy and How Does createScriptURL
Work?
At its core, a Trusted Types policy is a developer-defined set of rules that converts untrusted input (usually raw strings) into “trusted” objects. One key method is createScriptURL
:
Purpose:
It takes a raw URL string and validates it—typically ensuring it comes from an allowed domain—then returns aTrustedScriptURL
object. This trusted object can then safely be assigned to a<script>
element’ssrc
attribute.Example:
const myPolicy = trustedTypes.createPolicy('myPolicy', { createScriptURL: (url) => { if (url.startsWith('https://trusted-domain.com/')) { return url; // Implicitly converted to a TrustedScriptURL } throw new Error('Invalid script URL'); } });
In this example, only URLs starting with
https://trusted-domain.com/
are allowed. If a URL doesn’t meet this criterion, an error is thrown, preventing a malicious script from being loaded.
2. How Do Trusted Types Prevent Attacks?
Trusted Types enforce a strict discipline:
Sanitization Gate:
By requiring that sensitive sinks (likescript.src
) accept only objects created by a Trusted Types policy, the browser prevents direct assignment of raw, potentially dangerous strings.Immutable Policy Behavior:
Once a policy is created, its rules are fixed. This means that even if an attacker manages to run some script, they cannot change how your policy sanitizes input.CSP Integration:
When paired with a Content Security Policy (CSP) directive (e.g.,require-trusted-types-for 'script'
), the browser rejects any script-related operations that do not use aTrustedScriptURL
.
3. How Is Policy Creation Protected from Malicious Injection?
One common question is:
“What prevents a hacker from injecting code like window.trustedTypes.createPolicy('myPolicy', …)
and making their own ‘trusted’ policy?”
The answer lies in several key mechanisms:
Unique Policy Names:
A policy must be created with a unique name. If your application has already definedmyPolicy
, any attempt to recreate it will fail. This prevents an attacker from simply overriding your trusted configuration.Content Security Policy (CSP):
CSP directives such asContent-Security-Policy: script-src 'self' https://trusted-cdn.com; trusted-types myPolicy; require-trusted-types-for 'script'
enforce that only policies with the allowed name(s) can be used and that all script sinks require Trusted Types. Any policy injected with a different name would be blocked.
Immutable Policy Objects:
Even if an attacker somehow accesses the original policy object, its internal rules (e.g., the logic insidecreateScriptURL
) cannot be modified.
4. Reusing a Trusted Types Policy Across Your Application
You might wonder, “Can I retrieve an already created policy and apply it in different parts of my website?”
The answer is yes. When you create a Trusted Types policy, you typically store the returned object in a variable and reuse it across your modules:
Example Using a Module:
// In policy.js export const myPolicy = trustedTypes.createPolicy('myPolicy', { createScriptURL: (url) => { if (url.startsWith('https://trusted-domain.com/')) { return url; } throw new Error('Invalid URL'); } }); // In main.js import { myPolicy } from './policy.js'; function loadScript(url) { const trustedUrl = myPolicy.createScriptURL(url); const script = document.createElement('script'); script.src = trustedUrl; document.body.appendChild(script); } loadScript('https://trusted-domain.com/script.js');
This approach ensures consistent sanitization across your application without redefinition.
5. What Happens When You Attempt to Override or Create Duplicate Policies?
By design, the Trusted Types API does not allow redefinition of an existing policy with the same name. If you try to create a policy named myPolicy
twice, the second call will throw an error.
However, there is a twist when using CSP:
Using
allow-duplicates
:
With a CSP directive likeContent-Security-Policy: trusted-types myPolicy 'allow-duplicates'
you are instructing the browser to permit multiple policy instances with the same name.
Impact: Even though two distinct policy objects (say,
policy1
andpolicy2
) may share the namemyPolicy
, each retains its own rules. When you explicitly callpolicy1.createScriptURL(url)
, it uses the rules defined inpolicy1
—the existence ofpolicy2
does not affect it.Best Practice: Generally, you should avoid creating duplicate policies to maintain clarity and reduce risk.
6. Global Exposure and the Risk of Policy Overriding
A critical security question is:
“If I attach my policy instance to the global window
object (e.g., window.myPolicy
), can an attacker override it, causing subsequent code to use a malicious policy?”
The Risk
Global Variables Are Mutable:
Although the policy object itself (once created) cannot be modified, the variablewindow.myPolicy
can be reassigned by any script in the same execution context. If an attacker injects code that runs before your other modules, they could reassignwindow.myPolicy
to a new, malicious policy.Potential Impact:
Code that runs later and referenceswindow.myPolicy
would unknowingly use the attacker’s policy. This could allow the attacker to bypass your intended sanitization logic and load malicious scripts.
Mitigation Strategies
To prevent this vulnerability:
Encapsulate the Policy:
Instead of storing the policy in a globally accessible variable, encapsulate it within a module or closure. For example:const myModule = (() => { const myPolicy = trustedTypes.createPolicy('myPolicy', { createScriptURL: (url) => { if (url.startsWith('https://trusted-domain.com/')) { return url; } throw new Error('Invalid URL'); } }); return { loadScript: (url) => { const trustedUrl = myPolicy.createScriptURL(url); const script = document.createElement('script'); script.src = trustedUrl; document.body.appendChild(script); } }; })(); myModule.loadScript('https://trusted-domain.com/script.js');
This approach minimizes the risk by keeping
myPolicy
private.Strict CSP without
allow-duplicates
:
Removing theallow-duplicates
directive (when possible) enforces unique policy creation, making it harder for an attacker to create a competing policy.Minimize Global Exposure:
Only expose what is absolutely necessary to the global scope. Keeping security-critical objects out ofwindow
reduces the attack surface.
7. Conclusion
Trusted Types is a powerful security tool for mitigating XSS by forcing all sensitive data (like script URLs) through strict, developer-defined sanitization policies. When used correctly, they ensure that only validated input reaches critical DOM sinks. However, as our discussion reveals:
Policy Creation and Reuse:
Policies must be created once and reused consistently. Although CSP can allow duplicates viaallow-duplicates
, doing so comes with risks that must be managed carefully.Global Variable Risks:
Attaching a policy to a global object (e.g.,window.myPolicy
) can expose it to potential reassignment by malicious code. Encapsulation in modules and a strict CSP can help mitigate this risk.
By understanding these subtleties and following best practices—like encapsulating policies and limiting global exposure—you can leverage Trusted Types to greatly enhance your web application’s security.
Subscribe to my newsletter
Read articles from Pavlo Datsiuk directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Pavlo Datsiuk
Pavlo Datsiuk
🚀