diff --git a/public/og-default.png b/public/og-default.png index d43bbaef..bf1d4f12 100644 Binary files a/public/og-default.png and b/public/og-default.png differ diff --git a/public/og/checklist.png b/public/og/checklist.png index 3a53fd87..99d27d2d 100644 Binary files a/public/og/checklist.png and b/public/og/checklist.png differ diff --git a/public/og/spec.png b/public/og/spec.png index 9b0485ab..467432c5 100644 Binary files a/public/og/spec.png and b/public/og/spec.png differ diff --git a/public/og/spec/security.png b/public/og/spec/security.png index ed3d3fc7..ba1d1dc6 100644 Binary files a/public/og/spec/security.png and b/public/og/spec/security.png differ diff --git a/public/og/spec/security/trusted-types.png b/public/og/spec/security/trusted-types.png new file mode 100644 index 00000000..5de0a886 Binary files /dev/null and b/public/og/spec/security/trusted-types.png differ diff --git a/src/content/changelog/2026-06-23-trusted-types.md b/src/content/changelog/2026-06-23-trusted-types.md new file mode 100644 index 00000000..90d7b1c3 --- /dev/null +++ b/src/content/changelog/2026-06-23-trusted-types.md @@ -0,0 +1,8 @@ +--- +title: New page on Trusted Types +date: "2026-06-23" +type: added +relatedSlugs: [trusted-types] +--- + +Added a page on [Trusted Types](/spec/security/trusted-types/) — the browser mechanism that makes DOM injection sinks like `innerHTML` reject plain strings and demand a vetted typed value, switched on with the `require-trusted-types-for` and `trusted-types` CSP directives. It reached Baseline in February 2026 and closes the DOM-based XSS gap that a [nonce-based CSP](/spec/security/content-security-policy/) leaves open. diff --git a/src/content/spec/security/content-security-policy.md b/src/content/spec/security/content-security-policy.md index cc321e0b..85973dd0 100644 --- a/src/content/spec/security/content-security-policy.md +++ b/src/content/spec/security/content-security-policy.md @@ -6,7 +6,7 @@ summary: "A CSP tells browsers which sources of script, style, image, and frame status: recommended order: 30 appliesTo: [all] -relatedSlugs: [https-tls, frame-ancestors, subresource-integrity, permissions-policy, mixed-content, cross-origin-isolation, reporting-endpoints] +relatedSlugs: [https-tls, frame-ancestors, subresource-integrity, permissions-policy, mixed-content, cross-origin-isolation, reporting-endpoints, trusted-types] updated: "2026-06-08T20:15:00.000Z" sources: - title: "Content Security Policy Level 3" diff --git a/src/content/spec/security/reporting-endpoints.md b/src/content/spec/security/reporting-endpoints.md index 2baa05fa..8a8e6b7c 100644 --- a/src/content/spec/security/reporting-endpoints.md +++ b/src/content/spec/security/reporting-endpoints.md @@ -6,7 +6,7 @@ summary: "A response header that names HTTP endpoints to which the browser POSTs status: recommended order: 35 appliesTo: [all] -relatedSlugs: [content-security-policy, cross-origin-isolation, permissions-policy, monitoring-uptime] +relatedSlugs: [content-security-policy, cross-origin-isolation, permissions-policy, monitoring-uptime, trusted-types] updated: "2026-06-16T00:00:00.000Z" sources: - title: "Reporting API" diff --git a/src/content/spec/security/subresource-integrity.md b/src/content/spec/security/subresource-integrity.md index 66db5598..dc52899e 100644 --- a/src/content/spec/security/subresource-integrity.md +++ b/src/content/spec/security/subresource-integrity.md @@ -6,7 +6,7 @@ summary: "SRI adds a cryptographic hash to every third-party script and styleshe status: recommended order: 90 appliesTo: [all] -relatedSlugs: [content-security-policy, https-tls, x-content-type-options] +relatedSlugs: [content-security-policy, https-tls, x-content-type-options, trusted-types] updated: "2026-05-29T09:13:20.000Z" sources: - title: "Subresource Integrity (W3C Recommendation)" diff --git a/src/content/spec/security/trusted-types.md b/src/content/spec/security/trusted-types.md new file mode 100644 index 00000000..9ed4cfd3 --- /dev/null +++ b/src/content/spec/security/trusted-types.md @@ -0,0 +1,97 @@ +--- +title: "Trusted Types" +slug: trusted-types +category: security +summary: "Trusted Types make the browser reject plain strings at DOM injection sinks like innerHTML, demanding a vetted typed value instead. Switched on with two CSP directives, it neutralises a whole class of DOM-based XSS." +status: recommended +order: 95 +appliesTo: [all] +relatedSlugs: [content-security-policy, subresource-integrity, reporting-endpoints] +updated: "2026-06-23T10:00:00.000Z" +sources: + - title: "Trusted Types (W3C Working Draft)" + url: "https://www.w3.org/TR/trusted-types/" + publisher: "W3C" + - title: "MDN — Trusted Types API" + url: "https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API" + publisher: "MDN" + - title: "MDN — Content-Security-Policy: require-trusted-types-for" + url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/require-trusted-types-for" + publisher: "MDN" + - title: "OWASP — DOM based XSS Prevention Cheat Sheet" + url: "https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html" + publisher: "OWASP" +--- + +## What it is + +Trusted Types is a browser mechanism that blocks DOM-based cross-site scripting at the point of injection. DOM XSS happens when an attacker-controlled string reaches a dangerous "sink" — `innerHTML`, `outerHTML`, `document.write()`, `eval()`, or a script element's `src`. Trusted Types makes the browser refuse a plain string at those sinks and demand a non-spoofable typed value (`TrustedHTML`, `TrustedScript`, or `TrustedScriptURL`) produced by a policy you define and control. You enable it with two Content Security Policy directives: + +```http +Content-Security-Policy: require-trusted-types-for 'script'; trusted-types escape +``` + +It reached [Baseline](https://web.dev/baseline) in February 2026 — Chrome and Edge have shipped it since 2020, Safari since version 26, and Firefox completed the set. + +## Why it matters + +A nonce-based [CSP](/spec/security/content-security-policy/) stops an attacker injecting or running a new external script, but DOM XSS needs no new script element: it abuses code already on the page that writes untrusted input into a sink. Take a search widget that echoes the query from the URL: + +```js +// Vulnerable: q comes straight from the address bar +const q = new URLSearchParams(location.search).get("q"); +results.innerHTML = `

Results for ${q}

`; +``` + +A visitor sent to `?q=` runs the attacker's script in your origin — the `onerror` handler fires the moment the broken image loads, and the session cookie leaves the building. No external script, no CSP `script-src` violation; a nonce never gets a look-in. + +Trusted Types closes that gap. With `require-trusted-types-for 'script'` enforced, that `innerHTML` assignment throws a `TypeError` before the string ever reaches the parser, because `q` is a plain string and not a `TrustedHTML` value. To make the page work again you must route the value through a policy that sanitises it: + +```js +const policy = trustedTypes.createPolicy("escape", { + createHTML: (s) => DOMPurify.sanitize(s), +}); +results.innerHTML = policy.createHTML(`

Results for ${q}

`); +``` + +The `` is now stripped before it lands. The win is structural: the question stops being "did every developer remember to sanitise every sink?" and becomes "does *any* sink receive an unsanitised string?" — and the browser answers that for you, everywhere, by throwing. + +This covers more than `innerHTML`. The three trusted types guard the three families of script-execution sink: + +- **`TrustedHTML`** — markup parsers: `innerHTML`, `outerHTML`, `document.write()`, `insertAdjacentHTML()`, `