← Blog 5 min read

Trusted Types: How Browsers Block DOM XSS

Trusted Types: How Browsers Block DOM XSS

Many serious web security problems begin with an ordinary string. A URL parameter, message, or stored value reaches a browser API that interprets it as HTML or JavaScript. If hostile input crosses that boundary unchanged, the browser may execute code the application never intended to trust.

Trusted Types changes the contract at those dangerous entry points. Instead of accepting any string, supported browser sinks can be required to accept a typed value created by an approved policy. That makes DOM-based cross-site scripting harder to introduce and much easier to audit.

How Trusted Types blocks DOM XSS

DOM-based cross-site scripting, often shortened to DOM XSS, happens in client-side code. The server may deliver a perfectly valid page, but JavaScript later reads attacker-controlled data and sends it to an injection sink. Properties such as innerHTML, methods such as document.write(), and script URL assignments can turn text into executable behavior.

The Trusted Types API divides these destinations into HTML, script, and script-URL sinks. A site creates a named policy with transformation functions. That policy can produce TrustedHTML, TrustedScript, or TrustedScriptURL objects. When enforcement is enabled, a plain string sent to a protected sink triggers an error rather than being interpreted.

MDN's Trusted Types API guide explains the model and lists direct injection sinks. The current W3C Trusted Types specification defines the policies, objects, and enforcement behavior.

A trusted type is evidence of a process

The word “trusted” can be misunderstood. The browser does not decide that a piece of HTML is harmless. It confirms that the value passed through a developer-defined policy. The quality of that policy still matters.

For HTML, a policy often calls a maintained sanitizer with a narrow configuration. For script URLs, it may accept only a fixed group of destinations. Some applications can reject dynamic script text entirely. A policy that simply returns every input unchanged satisfies the type system while providing little security.

This is where Trusted Types complements the HTML Sanitizer API. Sanitization filters unwanted markup. Trusted Types controls how values reach dangerous sinks and creates a small set of reviewable policy points. They solve related but distinct parts of the problem.

Content Security Policy provides enforcement

The JavaScript API creates policies and trusted values. Content Security Policy turns the convention into a browser-enforced rule. The require-trusted-types-for 'script' directive requires Trusted Types for relevant DOM XSS sinks. The trusted-types directive restricts which policy names a page may create.

Teams can begin with a report-only CSP header. Violations are reported without breaking the page, which reveals legacy code that still sends strings to sinks. Developers can then replace or remove those paths before enabling enforcement.

A default policy can temporarily transform old string assignments, but MDN recommends it as a migration tool rather than a permanent hiding place. Explicit policies make the security boundary visible in code. Noorani's guide to Content Security Policy explains how CSP constrains scripts and other resources beyond Trusted Types.

Migration starts with the sinks

A practical rollout begins by inventorying injection sinks. Search for innerHTML, outerHTML, insertAdjacentHTML(), document.write(), dynamic script creation, and APIs that parse HTML strings. Each use should answer two questions: does this feature need HTML at all, and where did the value originate?

Many assignments can become textContent, DOM construction methods, or fixed templates. These avoid an HTML parsing boundary entirely. The smaller set that genuinely needs rich markup can move behind a sanitizer-backed policy.

Policy names should describe a narrow purpose, such as rendering reviewed article markup or selecting an approved script URL. A single global policy called “everything” may be convenient, but it makes review harder and encourages unrelated code to share assumptions.

Common mistakes weaken the guarantee

The first mistake is treating the trusted object as proof that the original source was honest. It only proves the policy ran. The second is creating policies in many modules, which spreads security decisions across the codebase. The third is enabling enforcement before understanding third-party widgets and older framework code that depend on string sinks.

Developers should also test failures. A rejected value needs a safe user experience, not a silent blank panel. Monitoring should record the responsible code path without collecting the sensitive content that triggered it. Security telemetry can itself become a privacy problem if raw form values or messages are copied into reports.

Trusted Types does not replace output encoding, authorization, server validation, dependency review, or secure headers. It targets a specific class of client-side injection. That focus is its strength.

What users should understand

Trusted Types usually has no visible permission prompt. It is a protection implemented by the website and enforced by the browser. Users will not see a badge proving that a page uses it, and its presence does not certify the entire service.

The practical signal is quieter: well-engineered sites treat the boundary between text and executable content as a security decision. They minimize that boundary, centralize the exceptions, and let the browser reject accidental shortcuts. Trusted Types gives development teams a precise way to do that.

Browse with more intention

Noorani brings prayer times, Qibla, tracker blocking, and privacy into one calm desktop browser built for how Muslims live online.

Download Noorani