何も考えずにelement.InnerHTML=hogeってやるとクロスサイトスクリプティングの完成です。
安全にHTMLを挿入したいという要望はそれはもちろんたいへん多く、古くは2018年にGoogleがSanitizer APIという提案を行いました。
しかし当時の提案は色々と問題があったようで、Chromeが先走って実装したものの紆余曲折の果てに撤退する羽目になりました。
またInnerHTMLとかの個別ではなくWebサイト全体で安全性を制御しようというContent Security Policyも策定されているのですが、これは導入が非常に面倒で正直やってられません。
2026年の段階でも普及率は10%も行っていない程度のようです。
普及の進まないContent Security Policyを尻目にSanitizer APIの改善は進み、そしてついに2026/02/24リリースのFirefox148・2026/03/10リリースのChrome146において本格実装されることとなりました。
Safariは2026/03時点では未対応ですが、肯定的ではあるのでそのうち実装されると思います。
使いかた
const hoge = document.getElementById("hoge");
hoge.setHTML("<b>太字</b><script>alert();</script>");
hogeの中身は<b>太字</b>になり、scriptタグは削除されます。
エスケープではなく削除です。
この関数によって、安全にHTMLを挿入することが可能になりました。
なお↑はinnerHTMLだったとしても実際は動かないんですが例ということで。
ということで以下はMozilla公式ブログからSanitizer APIの紹介記事、Goodbye innerHTML, Hello setHTML: Stronger XSS Protection in Firefox 148の紹介です。
Goodbye innerHTML, Hello setHTML: Stronger XSS Protection in Firefox 148
ネットから未だ根絶に成功していない脆弱性のひとつがクロスサイトスクリプティングXSSです。
新しいSanitizer APIは、信頼できないHTMLを安全にDOMに追加する簡単な方法を提供します。
Firefox148はこのセキュリティ機能を最も早く提供したブラウザであり、全ての人に、より安全なインターネットを提供します。
他のブラウザもすぐに追随してくれることを期待しています。
XSS脆弱性は、ユーザが生成した任意のHTML・JavaScriptをWebサイトに組み込んでしまうことで発生します。
この攻撃によって攻撃者はユーザインタラクションを監視・操作することが可能であり、脆弱性が修正されないかぎりユーザ情報を継続的に盗み続けます。
XSSは防ぐことが非常に難しく、10年の長きにわたり脆弱性トップ3の座にランクイン(CWE-79)し続けています。
Firefoxは2009年にContent-Security-Policyの策定を主導して以来、XSSに深く関わってきました。
CSPは、ブラウザが読み込んで実行できるリソースを制限するものであり、XSSの攻撃を防ぐ大きな防波堤となります。
しかしながら、CSPは既存ウェブサイトのアーキテクチャを大きく変更し、セキュリティ専門家による継続的な介入を必要とするため、十分に普及しているとは言い難い状態です。
Sanitizer APIは、悪意のあるHTMLを無害なHTMLに変換する、すなわちサニタイズする標準的な方法を提供することで、この問題を解決するために設計されています。
setHTML()メソッドは、HTMLを挿入する際にサニタイズ処理を実行し、デフォルトで安全にします。
以下は単純な例です。
document.body.setHTML(`<h1>Hello my name is <img src="x" onclick="alert('XSS')">`);
タグh1は許可されますが、imgタグとその要素onclickはDOMから削除され、XSS攻撃から防がれた安全なHTMLが生成されます。
<h1>Hello my name is</h1>
開発者は、諸悪の根源であるinnerHTMLをsetHTML()に置き換えることで、強力なXSS保護を最小限のコードで手に入れることができます。
また、より安全なWebエクスペリエンスを実現するため、Firefox148はSanitizer APIにくわえてTrusted Typesもサポートしています。
開発者は、これらの標準を採用することで、専門のセキュリティチームを必要とせず、大規模な仕様変更をすることもなく、XSSを防止することができます。
デフォルトで許可されているタグについて
デフォルトで使用可能なタグ一覧はこちらです。
リストは非常に長いので仕様書を見てください。
このリストはホワイトリストです。
すなわち、ここに載っていないタグや属性、および独自に作ったタグや属性は基本的に一切使えません。
以下ではリストの見方の例を示します。
{
"name": "b",
"namespace": "http://www.w3.org/1999/xhtml",
"attributes": []
},
{
"name": "del",
"namespace": "http://www.w3.org/1999/xhtml",
"attributes": [
{
"name": "cite",
"namespace": null
},
{
"name": "datetime",
"namespace": null
}
]
},
この例では、bタグは使用可能であり、個別に許可されるプロパティはありません。
delタグは使用可能であり、個別プロパティcite・datetimeを使用可能です。
個別ではなく常に使用可能な属性は別途定義されており、たとえばtitleやwhite-spaceなどはどのタグでも常に使用できます。
一方onerrorやonkeydownといった如何にも悪いことができそうな属性はもちろん許可されておらず、HTMLからは削除されます。
意外なことにstyleも入っていないので、style="xxx"と直接書いていた装飾は全て剥がされてしまいます。
データ属性data-xxxも一切使えません。
どうしてもstyleを使いたいんだよ、みたいな場合はsetHTML()の第二引数で許可することができます。
const customSanitizer = { sanitizer: { allowAttribute: ["style"] } };
hoge.setHTML('<b style="color:red;">hoge</b>'); // styleが消える
hoge.setHTML('<b style="color:red;">hoge</b>', customSanitizer); // styleが消えない
もちろんやりすぎると脆弱性になってしまってSanitizerの意味がなくなるので気をつけましょう。
たとえばimgタグはデフォルト不許可になっていて、これは制限が厳しいと感じるかもしれません。
しかし、これを許可するとXSSを仕込める可能性が出てきます。
感想
もっと堅牢で安全性の高いContent-Security-Policyという仕様が大昔からあるわけですが、これはサイト全体を書き換える必要があって導入の敷居が高く、設定もやたら多くて導入も運用もつらい、ということでさっぱりまったく普及していません。
ということで、手の届かない理想の平和より、目の前の問題をとりあえずどうにかするための泥臭い解決策が導入されました。
こちらは野良ライブラリと異なりブラウザ自らが提供する安全性の高いエスケープ機能であり、そして何より導入が極めて容易です。
とりあえずinnerHTMLは全てsetHTML()に置換する、くらいの勢いで導入していいのではないでしょうか。
ちなみにsetHTMLUnsafe()というメソッドもありまして、これ名前からしてsetHTML()と対にして導入されたんだろうなあと思えば実は全く関係なくDeclarative Shadow DOMに伴って導入されたものだそうです。
わかりにくい。

