innerHTML は文字列を HTML として解釈するため、ユーザー入力をそのまま渡すと XSS 脆弱性の原因になります。より安全な代替手段を紹介します。
textContent
テキストのみを扱う場合は textContent を使います。HTML として解釈されないため、悪意のあるスクリプトが実行されることはありません。
const div = document.getElementById('output') div.textContent = '<script>alert("XSS")</script>' // → 文字列がそのまま表示され、スクリプトは実行されない
DOM 操作メソッド
要素を動的に構築する場合は、createElement と appendChild を組み合わせます。
const ul = document.getElementById('list') const items = ['<b>Apple</b>', 'Banana', 'Cherry'] items.forEach(item => { const li = document.createElement('li') li.textContent = item // HTML として解釈されない ul.appendChild(li) })
この方法では各要素を個別に作成するため、構造が明確になり、意図しない HTML の混入を防げます。
insertAdjacentText
特定の位置にテキストを挿入する場合は insertAdjacentText が便利です。insertAdjacentHTML と異なり、文字列を HTML として解釈しません。
const div = document.getElementById('container') div.insertAdjacentText('beforeend', '<script>危険</script>') // → テキストとして安全に挿入される
setHTMLUnsafe と Sanitizer API
信頼できない HTML を扱う必要がある場合、Sanitizer API を検討します。危険なタグや属性を除去してから DOM に挿入できます。
const dirty = '<img src=x onerror=alert("XSS")><b>太字</b>' const sanitizer = new Sanitizer() document.getElementById('output').setHTML(dirty, { sanitizer }) // → <b>太字</b> のみが挿入され、img タグは除去される
Sanitizer API は比較的新しい機能のため、ブラウザの対応状況を確認してください。対応していない環境では DOMPurify などのライブラリで代用できます。
template 要素と cloneNode
複雑な構造を繰り返し生成する場合は <template> 要素を活用します。
<template id="card-template"> <div class="card"> <h2 class="title"></h2> <p class="body"></p> </div> </template>
const template = document.getElementById('card-template') const clone = template.content.cloneNode(true) clone.querySelector('.title').textContent = userInput.title clone.querySelector('.body').textContent = userInput.body document.body.appendChild(clone)
テンプレートの構造は固定され、動的な値は textContent で安全に挿入されます。