Trusted TypesでDOM XSSを大幅に軽減する
Trusted Typesは、innerHTMLやeval()などのシンクに対して型付きオブジェクトの使用を強制し、DOM XSSを大幅に軽減するブラウザAPIです。CSPヘッダーによる強制やポリシーの作成を中心に解説します。
はじめに
DOM XSSは、innerHTMLやdocument.writeなどのDOM APIに攻撃者が制御可能な文字列が渡されることで発生します。
// ユーザー入力をそのままinnerHTMLに渡してしまう
const userInput = '<img src=x onerror="alert(document.cookie)">';
el.innerHTML = userInput; // XSSが実行される
この種の脆弱性は、コードレビューやLinterで注意を払っても完全には防ぎきれず、プロジェクトが大きくなるほどリスクが高まります。
Baseline 2026に追加されたTrusted Typesは、これらの危険なDOM API(シンク)に対して生の文字列ではなく型付きオブジェクトの使用を強制することで、DOM XSSを大幅に軽減するブラウザAPIです。
CSPヘッダーによる強制
Trusted Typesは、Content-Security-Policyヘッダーで強制します。
Content-Security-Policy: trusted-types myPolicy
trusted-typesディレクティブは、作成できるポリシー名を制限します。
この例ではmyPolicy以外の名前でtrustedTypes.createPolicy()(後述)を呼び出すとエラーになります。
これにより、サードパーティスクリプトなどが勝手にポリシーを作成することを防げます。
trusted-typesの宣言では作成を制限するだけで、シンクに生の文字列を渡すことは禁止できません。
禁止するにはrequire-trusted-types-for 'script'を追加します。
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types myPolicy
この設定が有効な状態でシンクに生の文字列を渡すと、TypeErrorがスローされます。
el.innerHTML = '<b>Hello</b>';
// TypeError: Failed to set the 'innerHTML' property on 'Element':
// This document requires 'TrustedHTML' assignment.
段階的に導入する場合は、Content-Security-Policy-Report-Onlyヘッダーを使って違反をレポートのみにすることもできます。
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; trusted-types myPolicy; report-uri /csp-endpoint
ポリシーの作成
trustedTypes.createPolicy()でポリシーを作成します。
ポリシーはシンクに渡す値の検証やサニタイズを一箇所にまとめる仕組みです。
第1引数にはCSPのtrusted-typesディレクティブで許可したポリシー名を、第2引数にはコールバック関数を定義します。
const escapePolicy = trustedTypes.createPolicy('myPolicy', {
createHTML: (input) => DOMPurify.sanitize(input),
});
コールバック関数は用途に応じて3種類あります。
createHTML→TrustedHTMLを返す。innerHTMLやdocument.write()などに対応createScript→TrustedScriptを返す。eval()やsetTimeout(string, ms)などに対応createScriptURL→TrustedScriptURLを返す。HTMLScriptElement.srcやnew Worker()などに対応
シンクはTrustedHTML・TrustedScript・TrustedScriptURLのいずれかしか受け付けないため、原則としてTrustedTypePolicyのインスタンスメソッド経由で作成した値しか渡せません。
el.innerHTML = escapePolicy.createHTML(userInput);
// DOMPurifyによりサニタイズされた安全なHTMLが設定される
デフォルトポリシー
defaultという名前のポリシーは特別な役割を持ちます。
シンクに生の文字列が渡された際、ブラウザが自動的にデフォルトポリシーのコールバックを呼び出します。
trustedTypes.createPolicy('default', {
createHTML: (input) => DOMPurify.sanitize(input),
});
// デフォルトポリシーが自動的に適用される
el.innerHTML = '<b>Hello</b><script>alert(1)</script>';
// DOMPurifyによりscriptタグが除去される
作成したデフォルトポリシーはtrustedTypes.defaultPolicyから参照できます。
デフォルトポリシーは既存のコードベースへの移行手段として設計されています。 変更が難しいサードパーティスクリプトへの対応にも有効ですが、ルールが緩くなりがちでTrusted Typesのメリットが薄まるため、最終的には各シンクで明示的にポリシーを使うようリファクタリングすることが推奨されます。
ユーティリティ
このほかにもtrustedTypesから直接扱える便利なプロパティとメソッドが用意されています。
emptyHTMLはポリシーを経由せずに空のHTMLをシンクに渡すためのプロパティで、TrustedHTMLを返します。
emptyScriptは空のTrustedScriptを返します。
el.innerHTML = trustedTypes.emptyHTML;
scriptEl.text = trustedTypes.emptyScript;
isHTML()・isScript()・isScriptURL()は値が対応するTrusted Typeかどうかを判定します。
// TrustedTypeかどうかを判定するユーティリティ
trustedTypes.isHTML(value); // true TrustedHTMLの場合
trustedTypes.isScript(value); // false TrustedHTMLはScriptではない
trustedTypes.isScriptURL(value); // true TrustedScriptURLの場合
getPropertyType()・getAttributeType()は特定の要素のプロパティや属性にどのTrusted Typeが必要かを確認できます。
// 特定の要素・属性にどの型が必要かを確認
trustedTypes.getPropertyType('div', 'innerHTML'); // TrustedHTML
trustedTypes.getPropertyType('script', 'text'); // TrustedScript
trustedTypes.getAttributeType('script', 'src'); // TrustedScriptURL
// Trusted Typesが必要ない属性はnullが返る
trustedTypes.getAttributeType('img', 'src'); // null
おわりに
Trusted Typesは、シンクに型付きオブジェクトの使用を強制することで、DOM XSSを大幅に軽減するAPIです。 サニタイズ処理をポリシーに集約できるため、セキュリティレビューの対象をポリシーの実装に絞ることができます。
Content-Security-Policy-Report-Onlyヘッダーを使えば既存のコードに影響を与えずに違反箇所を可視化できるので、まずはレポートモードから気軽に導入してみましょう。
ただし、Trusted TypesはCSP全体の防御戦略の一部であり、script-srcなど他のディレクティブと合わせた設計・検証が重要です。