Trusted Types is a proposed browser feature that aims to prevent DOM-based cross-site scripting (DOM XSS) vulnerabilities by restricting the use of untrusted strings in dangerous DOM sink functions. It works by requiring developers to use strongly typed policy objects instead of plain strings, making it easier to write secure code and reducing the DOM XSS attack surface. The design goals are to empower secure development, integrate smoothly with existing code, and allow gradual migration without breaking applications.
1. Trusted Types
and the end of DOM XSS
Krzysztof Kotowicz, Google
@kkotowicz
koto@google.com
2. What if we could...
● Fix the root cause of DOM XSS
● Help developers write secure code
● Simplify security reviews
● Dramatically reduce the attack surface
● Without breaking our applications?
4. DOM XSS refresher
● Purely client-side XSS variant
● Data read from user controlled source is passed to a DOM XSS sink
● Example:
location.hash ⇒ … ⇒ bar.innerHTML
https://example.com/#<img src=x onerror=alert(1)>
5. Why do we still have DOM XSS?
Easy to
introduce
Hard to
detect
&
6. ● It's not just innerHTML
● Many string -> code functions (DOM XSS sinks)
● They accept URLs, HTML, or Javascript code
DOM API is not secure by default
eval('foo()');
document.createElement('div').innerHTML = '<foo>';
document.createElement('script').src = '//foo';
document.createElement('a').setAttribute('onclick', 'foo()');
document.createElement('div').insertAdjacentHTML('beforebegin',
'<foo>');
new DOMParser().parseFromString('<foo>', 'text/html');
window.open('//foo');
> 60
sinks
7. ● Sources far from sink, complex data flows
● JavaScript is dynamic
● DOM XSS lurks in the shadows: script gadgets
It's hard to analyze code
// What is bar?
foo.innerHTML = bar
// Non-deterministic value
function getPropertyName() {
if (condition)
return 'innerHTML';
}
foo[getPropertyName()] = bar
8. Growing problem
● DOM sinks can be used by your own code
● … or the libraries you use
● … or the scripts you load (analytics?)
● … or the script that they load at runtime.
● Each of those potentially adds DOM XSS.
● Applications grow in size.
● It's untenable to write & deploy DOM-XSS free apps
● At Google, DOM XSS is already the most common XSS variant.
9. We know how to address it!
Safe Types (link)
● 6+ years of experience
● Protects Gmail and almost all other
Google applications
● Evidence of efficacy
● Securely produce values that end up in DOM
● Implemented as Java{Script},Go, … libraries
● We're porting this approach directly to the browsers
11. Design principles
● Empower the developers to:
○ Write secure code easy
○ Use the secure by default APIs
○ Get early feedback
● Empower the security professionals to:
○ Control the security-relevant code
○ Be looped-in when needed
○ Review the application
● Integrate with the existing ecosystem
○ Don't break the web!
○ Be backwards-compatible
○ Leverage existing solutions
12. Main idea
● Don't pass (HTML, URL, script URL) strings to the DOM sinks
● Use objects instead
● DOM already supports it:
● Instead of plain JS objects, use typed objects
○ TrustedHTML, TrustedScript, TrustedURL, TrustedScriptURL
● Make DOM sinks reject strings, and accept only the matching type
el.innerHTML = {toString: function() {return 'hello' }};
el.innerHTML // 'hello'
13. DOM sinks reject the strings:
DOM sinks accept the typed objects:
Enforcement mode
element.innerHTML = aString;
Content-Security-Policy: trusted-types *
element.innerHTML = aTrustedHTML;
14. DOM sinks accept & report the strings:
DOM sinks accept the typed objects:
Report-only
element.innerHTML = aString;
element.innerHTML = aTrustedHTML;
Content-Security-Policy-Report-Only: trusted-types *; report-uri https://
15. DOM sinks accept the strings:
DOM sinks accept the typed objects:
No enforcement
element.innerHTML = aString;
element.innerHTML = aTrustedHTML;
Content-Security-Policy: -
16. Creating the types
● First, create policies that define validation rules
● Policies have a unique name
● Use the policies to create Trusted Type objects
export const SanitizingPolicy = TrustedTypes.createPolicy('sanitizing', {
createHTML(s: string) => myCustomSanitizer(s)
}, false);
import {SanitizingPolicy} from './security/sanitizing-policy.ts';
// Calls myCustomSanitizer(foo).
const trustedTHML = SanitizingPolicy.createHTML(foo);
element.innerHTML = trustedHTML;
17. ● You can create policy named "default"
● It will be called as a fallback when you're using a string with a sink.
TrustedTypes.createPolicy('default', {
createHTML(s) {
console.log("fix me plz", s);
return s;
}
}, true)
Default policy
19. Control policy creation:
● Policy name whitelist:
● No duplicate policy names
Control policy usage:
● Policies are JavaScript objects
● Lock them in a module, inside a local function variable etc.
Control over policies
Content-Security-Policy: trusted-types sanitizing other
No drive-by policies!
20. Control over policies
(function() {
// Seemingly unsafe policy
const unsafePolicy = TrustedTypes.createPolicy('main-component', {
createHTML: (s) => s,
});
// No XSS because of the usage limitation
unsafePolicy.createHTML(`<div>My application component<div>`)
})();
21. Reduced attack surface
● The risky data flow will always be:
source ⇒ … ⇒ policy ⇒ Trusted Type ⇒ … ⇒ … ⇒ DOM sink
● Policies are secure => Application is secure
● No access to the policy object? Cannot introduce DOM XSS!
● Dramatically minimize the trusted codebase, simplify reviews
Benefits
22. Strongly typed API
● Easier to inspect statically
● Enabling code completion, linting, documentation, automatic refactoring
● Security validation at compile time … and at runtime
Backwards compatibility
● Use types in place of strings with no breakage
Complements other security solutions
● E.g. nonce based CSP for server-side XSS + Trusted Types for DOM XSS
Benefits
24. Trusted Types in practice
● DOM XSS sink functions are not called too often
● Policies in a few trusted components
○ Frameworks
○ Templating engines
○ HTML sanitizers (DOMPurify)
● Few misbehaving dependencies
● Code size overhead negligible
○ 66 bytes of the smallest polyfill
○ ~300 bytes in Google Closure
Porting modern applications is easy (we're already doing this!)
25. No!
● Designed to integrate smoothly with frameworks
○ Sample
● Helper libraries, tool integrations (WIP)
● Automatic refactorings for common use cases
○ E.g. Sinks called with string literals
● Support for a gradual migration
○ Start using TT without enforcement
○ Write a default policy to catch-all
○ Identify & address missing pieces through a default policy
○ Toggle report-only enforcement
○ Enforce when ready
Is it hard to migrate?
26. ● Support in Chromium browsers (origin trial - tinyurl.com/try-trusted-types)
● Discussion group - trusted-types@googlegroups.com
● W3C specification draft - https://wicg.github.io/trusted-types/
● Polyfills & documentation at https://github.com/WICG/trusted-types
● Adopting in Google applications
Working on external integrations:
● DOMPurify
● TypeScript type definitions - http://definitelytyped.org/
● Trials with Angular (sample patch), React, more to come…
● Secure policies library
● <your-project-here>
Project status