Prevent DOM-based XSS with Trusted Types and Content Security Policy with report-uri
With Trusted Types enabled, when a plain string is passed to a so-called sink like the innerHTML
or the document.write()
method without it being escaped by the Trusted Types policy, a report is generated by the browser.
Because the policy is enforced, the string will not actually be passed to the sink, protecting your app against DOM-based cross-site scripting (XSS) attacks.
The CSP response header:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri https://maine.has.report/report
-
require-trusted-types-for 'script'
: enable Trusted Types for DOM XSS sinks ('script'
is the only available value) -
report-uri
: where to send violation reports to- Reporting would also work with the
report-to
directive, see the CSP demo, but let's keep things simple here
- Reporting would also work with the
Trusted Types with a custom policy
#html
sink: …
/ #xss
sink: …
<script>
const escapePolicy = trustedTypes.createPolicy('escapePolicy', {
createHTML: string => string.replaceAll('<', '<'),
});
document.getElementById('prompt-html').onclick = function() {
const html = prompt('Enter any HTML', 'foo <strong>bar</strong>');
if (html) {
document.getElementById('html').innerHTML = escapePolicy.createHTML(html);
document.getElementById('xss').innerHTML = html;
}
}
</script>
The #html
sink:
- Is allowed in a way that it will display the HTML you've entered, escaped
-
Uses the
createHTML()
method of theescapePolicy
created bytrustedTypes.createPolicy()
- … to replace all
<
("less than") characters with<
entity, a very simple and naive escaping function
- … to replace all
-
If you enter let's say
<em>
createHTML()
will convert it to<em>
- Wrap it in a
TrustedHTML
object - And pass it to the
innerHTML
sink
- Note that your browser, not the policy, will convert
>
to>
as well, so in Developer Tools you will see<em>
#xss
sink:
- Is blocked, the HTML you've entered will not be passed to the
innerHTML
property - Because it doesn't use the
createHTML()
method of theescapePolicy
- But it tries to pass a plain string which is forbidden with Trusted Types enabled
- Will trigger a report, check Developer tools (Network and Console tabs)
- Check your reports
Related specs & documents
- My article about DOM XSS and Trusted Types (also available in Czech)
- Trusted Types Editor's Draft
- Prevent DOM-based cross-site scripting vulnerabilities with Trusted Types on web.dev
- Trusted Types API on MDN