世界を動かす技術を、日本語で。

Element: setHTML() メソッド

概要

  • setHTML() はHTML文字列を安全にDOMへ挿入するためのメソッド
  • XSS対策 として、危険な要素や属性を自動で除去
  • SanitizerやSanitizerConfig によるカスタマイズ可能
  • Element.innerHTMLsetHTMLUnsafe() より安全
  • Trusted Types API による検証は行われない

setHTML() メソッドの概要

  • setHTML(input, options) は、HTML文字列を安全にパースし、要素内に挿入するDOMメソッド
  • input :サニタイズ対象となるHTML文字列
  • options(省略可能) :サニタイズ方法を指定するオブジェクト
    • sanitizer :Sanitizerインスタンス、SanitizerConfigオブジェクト、または"default"文字列を指定可能
  • 戻り値 :なし(undefined)
  • 例外 :TypeError
    • 不正なSanitizerConfig (allowedとremoved両方指定など)、"default"以外の文字列、型不一致時に発生

setHTML() の動作詳細

  • XSS対策 として、HTML文字列から危険な要素や属性を自動で削除
  • 要素の文脈依存性 を考慮し、無効な要素(例:<col><table>外にある場合)も除去
  • Sanitizer設定 で許可されていても、危険な要素・属性は必ず削除
  • options.sanitizer 未指定時は デフォルトSanitizer が適用
    • デフォルトは「安全」と判断された要素・属性のみ許可
  • カスタムSanitizer/SanitizerConfig で許可・除去要素や属性、コメントなどを柔軟に指定可能
  • Sanitizer.removeUnsafe() が暗黙的に呼ばれるため、常に安全性を確保

使用推奨シーン

  • Element.innerHTML の代替として、信頼できないHTML文字列の挿入
  • Element.setHTMLUnsafe() の代替(安全性を重視する場合)
  • XSSリスク低減 を目的としたDOM操作

具体的な使い方

  • デフォルトSanitizerの場合
    • 例:
      const unsanitizedString = "abc <script>alert(1)</script> def";
      const target = document.getElementById("target");
      target.setHTML(unsanitizedString); // <script>タグは除去される
      
  • カスタムSanitizerの利用
    • 例:
      const sanitizer1 = new Sanitizer({ elements: ["div", "p", "button", "script"] });
      target.setHTML(unsanitizedString, { sanitizer: sanitizer1 }); // script要素も除去
      
  • SanitizerConfigを直接指定
    • 例:
      target.setHTML(unsanitizedString, { sanitizer: { removeElements: ["div", "p", "button", "script"] } });
      

ライブデモの流れ

  • HTML構成
    • 複数のボタン(Default, allowScript, Reload)
    • 挿入対象の<div>とログ表示用<pre>
  • JavaScript処理
    • sanitizer適用ボタン でsetHTMLを実行し、サニタイズ前後の内容をログに出力
    • defaultSanitizer :危険な要素・属性はすべて除去
    • allowScriptSanitizer :script要素を許可してもsetHTMLでは除去される
    • Reloadボタン :ページリロードで初期化
    • Sanitizer未対応ブラウザ :代替処理案内

結果・注意事項

  • どのSanitizer設定でも<script>要素やonclick属性は除去
  • setHTML()は常に安全性を最優先
  • Trusted Types API による追加の検証は行われない点に留意

参考リンク

  • HTML Sanitizer API仕様 :HTML Sanitizer API# dom-element-sethtml
  • 関連API
    • Element.setHTMLUnsafe()
    • ShadowRoot.setHTML(), ShadowRoot.setHTMLUnsafe()
    • Document.parseHTML(), Document.parseHTMLUnsafe()
  • 公式ドキュメント参照推奨

Hackerたちの意見

つまり、これは基本的にinnerHTMLの安全版ってこと?

そうだね、もうちょっと適切に言うと、これは内蔵のDOMPurifyって感じかな(dompurifyは、HTMLを注入する前にサニタイズするためによく使われるnpmパッケージ)。

HTMLを生成してインジェクトしてるのに、なんで「安全な」バージョンが必要なのか、ちょっと混乱してる。

今週、Firefox Nightly(だけ)でこれをデフォルトで有効にしたよ。

Litがベースラインに達したら、これを使うのがめっちゃ楽しみ!lit-htmlのテンプレートは、テンプレート文字列が改ざんできないからXSS対策がされてるけど、unsafeHTML()みたいなユーティリティもあって、信頼できない文字列をHTMLとして扱えるんだ。でも、今のところそれは…危険なんだよね。Element.setHTML()を使えば、safeHTML()ディレクティブを作って、開発者がサニタイズオプションを指定できるようにできるよ。

25年もこれなしでやってきたから、やっと見られて嬉しい!DOM APIの明らかな欠けてる部分だと思ってたし、なんでこんなに時間がかかったのか未だにわからない。でも、やっと実現したことが嬉しいし、これを実現するために頑張ってくれた人たちには感謝してる。

そうだね、リプライが来たね。

サニタイザーの設定で許可されていないHTMLエンティティを削除し、さらにXSSに対して安全でない要素や属性も削除します — サニタイザーの設定で許可されているかどうかに関わらず。強調は俺の。 この設計選択が理解できない。scriptタグを明示的に許可しているのに、なんでそれが削除されるの?もしこのメソッドがsetXSSSafeSubsetOfHTMLだったら理解できるけど、setHTMLでオーバーライド不可能なフィルターがあるのは変な感じ。

XSSに対して安全でないサニタイザーを使いたいなら、setHTMLUnsafeを使わなきゃ。

安全なデフォルトを目指してるんだろうね…ドキュメントをちゃんと読まない人や、動的に生成されたHTMLの出所を気にしない人が「setHTML()」を使うだろうから。その一方で、「setHTMLUnsafe()」もあるし、もちろんお馴染みの.innerHTMLもね。

これは主に使いやすさを考えた追加だから、危険な足元をより使いやすくするのはあまり意味がないと思う。危険なことをするためにinnerHTMLなどを使うことはできるしね。

それって、setHTMLを再度呼び出せるコードを許可することで、権限をエスカレートさせる引き金になっちゃうんじゃない?

Hacker Newsで議論の続きを見る