sanitize-htmlの導入
ユーザーが入力した内容を画面にそのまま表示するプレビュー画面が必要になるケースがしばしばあります。
最近の業務でユーザーのメッセージ内容をそのままレンダリングするロジックを扱う中で、XSS(クロスサイトスクリプティング)攻撃のリスクに気づき、これを防ぐためにsanitize-htmlを導入しました。
今回のポストでは、私が実際に使用したsanitize-htmlライブラリの活用方法と、その過程で経験したことを共有します。
なぜsanitize-htmlが必要だったのか?
ユーザーが入力したテキストを画面にレンダリングする際にHTMLタグが混在していると、以下のようなセキュリティ問題が発生する可能性があります。
- 悪意のある
<script>
タグを含めてブラウザでスクリプトが実行される可能性(XSS攻撃) - 想定外のHTMLタグやCSSスタイルがページレイアウトを崩す可能性
これらの問題を解決するために、入力されたHTMLコンテンツから許可されていないタグを除去する処理が必須です。このときに使用するライブラリがsanitize-htmlです。
sanitize-htmlライブラリ
sanitizeは「消毒する」という意味です。
sanitize-htmlは文字通り、HTML文字列から危険なタグや属性を除去するライブラリです。
特徴
- 許可するHTMLタグを設定
sanitizeHtml(html, {
allowedTags: ['b', 'i', 'em', 'strong', 'a'],
})
- 許可する属性の詳細管理
<a>
タグもhrefやhttpsのみ許可するように詳細設定が可能です。
sanitizeHtml(html, {
allowedTags: ['a'],
allowedAttributes: {
'a': ['href', 'target']
},
allowedSchemes: ['https'],
})
- XSS攻撃の防止
実際の実装例
// <script>削除、<html>のテキストを残す
export const toSanitized = (html: string): string => {
const tagPattern = /<\/?[a-z][a-z0-9]*\b[^>]*>/gi
// '<, >'などを単体で使う場合もあるので、html形式の時だけ変換する
if (!tagPattern.test(html)) return html
return sanitizeHtml(html, {
allowedTags: [], // すべてのHTMLタグを削除
allowedAttributes: {}, // すべてのHTML属性を削除
})
}
まず、正規表現で入力値がHTML形式かどうかを検査します。(tagPattern)
HTMLタグがなければそのまま早期リターンします。
HTMLタグが含まれる場合にはsanitize-htmlを呼び出し、全てのタグと属性を完全に除去して純粋なテキストのみを残すように処理します。
これにより、安全な文字列を取得してユーザーに表示できるようになりました。
入力ち: <p>text</p>
result: text
🤔 もし許可したいタグや属性がある場合は?
const clean = sanitizeHtml(dirty, {
allowedTags: [ 'b', 'i', 'em', 'strong', 'a' ],
allowedAttributes: {
'a': [ 'href' ]
},
allowedIframeHostnames: ['www.youtube.com']
});
上記のように、許可するタグと属性を追加すればOKです。
詳細は以下を参照してください 👉 sanitize-html
注意点
- パフォーマンスへの影響
sanitize-htmlは内部でHTMLの解析・整形処理を行うため、演算コストが発生します。
入力値が多い場合や頻繁に実行する場合は事前にテストが必要です。 - 表現の制約, 意図しない変形のリスク
全てのマークアップを除去すると、意図しない変形が発生する可能性があります。ほとんどのリスクは<script>
タグに起因するため、仕様やデザイン上で許可すべきタグや属性がある場合は適切に調整してください。
おわりに
今回は、セキュリティインシデントが発生してから対処するのではなく、ユーザー入力がチャット画面やプレビュー画面にそのまま出力されることを事前に発見して予防できた点に意義があります。
この経験を通じて、ユーザー入力は決して信頼できないということを改めて認識しました。
単に入力値をそのまま表示するだけで済むわけではなく、全ての入力に対して必ず適切なサニタイズ処理を行い、フロントエンド開発者であっても常にXSSなどのセキュリティリスクを念頭に置きながら開発する必要があることを学びました。