値の安全な出力方法とは何か
「安全な出力」とは、外部データやユーザー入力を画面に表示・属性へ設定する際に、HTMLとして解釈・実行されないように扱うことです。ここが重要です:構造(HTML)は固定し、可変データは“文字”として出力するのが基本方針です。これだけで HTML インジェクションや XSS の大半を防げます。
基本原則(構造とデータの責務分離)
テキストは textContent(または createTextNode)で入れ、属性は setAttribute/dataset で文字列として設定します。innerHTML の使用は、固定テンプレート+エスケープ済みの動的部分に限定します。ここが重要です:出力経路を「文字専用」にしておくと、見た目の変更があっても安全性が崩れません。
テキストの安全な出力(textContent・createTextNode・insertAdjacentText)
基本は textContent
<p id="msg"></p>
<script>
const msg = document.getElementById("msg");
const userText = '<script>alert(1)</script>'; // 外部入力の想定
msg.textContent = userText; // タグとして実行されず、文字として表示(安全)
</script>
HTMLここが重要です:textContent は“文字だけ”を扱うため、最も安全で高速です。UI の文言差し替えは原則 textContent に寄せます。
部分的に差し込みたいなら createTextNode
<p id="line"><b>重要:</b> </p>
<script>
const line = document.getElementById("line");
const t = document.createTextNode("更新があります");
line.appendChild(t); // 既存構造を壊さず、文字だけ追加
</script>
HTMLここが重要です:構造はそのまま、文字だけ追加。イベント消失などの副作用を避けられます。
その場に文字を挿入する insertAdjacentText
<p id="log"></p>
<script>
const log = document.getElementById("log");
log.insertAdjacentText("beforeend", "処理が完了しました"); // 文字のみ(安全)
</script>
HTMLここが重要です:insertAdjacentText は “HTMLではなく文字” を挿入します。小さな追記に便利です。
属性の安全な出力(setAttribute・dataset・boolean属性)
setAttribute で文字として設定
<a id="link">詳細</a>
<script>
const a = document.getElementById("link");
const title = 'ようこそ "新規" さん';
a.setAttribute("title", String(title)); // 文字列として属性に設定
</script>
HTMLここが重要です:属性は最終的には文字列。String(…) で明示的に文字列化すると、null/undefined 由来の事故を防げます。
data-* は文字専用、複合データは JSON に
<button id="buy">購入</button>
<script>
const btn = document.getElementById("buy");
const payload = { id: 42, tags: ["new", "sale"] };
btn.setAttribute("data-payload", JSON.stringify(payload));
const parsed = JSON.parse(btn.getAttribute("data-payload") ?? "{}");
</script>
HTMLここが重要です:配列やオブジェクトを dataset に直接入れない。JSON.stringify/parse のペアで扱うと一貫して安全です。
boolean属性は「有無」で管理(”false”は付けない)
<input id="agree" type="checkbox">
<script>
const el = document.getElementById("agree");
el.toggleAttribute("disabled", true); // 付ける → 有効
el.toggleAttribute("disabled", false); // 外す → 無効化しない
</script>
HTMLここが重要です:disabled/checked/required などは値の文字列ではなく有無が真偽。setAttribute(“disabled”, “false”) でも “存在する” ため有効になります。
HTMLをどうしても差し込む場合の限定策(エスケープとテンプレート)
動的部分を必ずエスケープしてから innerHTML
<p id="notice"></p>
<script>
const notice = document.getElementById("notice");
const user = '田中<script>alert(1)</script>';
function escapeHTML(s) {
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]);
}
notice.innerHTML = `ようこそ、<strong>${escapeHTML(user)}</strong> さん`;
</script>
HTMLここが重要です:構造(p/strong)は固定、動的部分は全面エスケープ。可能なら innerHTML を避け、構造は HTML/DOM、文字は textContent で。
template 要素で安全に構造を用意
<template id="row">
<li><span class="name"></span> <span class="qty"></span></li>
</template>
<ul id="list"></ul>
<script>
const tpl = document.getElementById("row");
const list = document.getElementById("list");
const frag = document.createDocumentFragment();
const data = [{name:"Apple",qty:3},{name:"Banana",qty:0}];
data.forEach(d => {
const node = tpl.content.cloneNode(true);
node.querySelector(".name").textContent = d.name;
node.querySelector(".qty").textContent = String(d.qty);
frag.appendChild(node);
});
list.appendChild(frag);
</script>
HTMLここが重要です:テンプレートは実行されず安全。可変データは textContent で埋めると、XSS を避けつつ柔軟にレンダリングできます。
URLやクエリの安全な生成(エンコードとAPIの活用)
ユーザー入力は必ずエンコード
<a id="search">検索</a>
<script>
const search = document.getElementById("search");
const q = "赤い 果物&人気";
search.setAttribute("href", `/find?q=${encodeURIComponent(q)}`);
</script>
HTMLここが重要です:未エンコードの混入は壊れたURLや意図しない解釈の原因。encodeURIComponent を徹底します。
複数パラメータは URLSearchParams
const params = new URLSearchParams({ q: "バナナ", page: 2, sort: "asc" });
link.setAttribute("href", `/items?${params}`);
JavaScriptここが重要です:文字列連結より URLSearchParams の方が確実・読みやすい。
実践例:安全なメッセージとリンクの出力
<p id="message"></p>
<a id="help">ヘルプ</a>
<script>
const message = document.getElementById("message");
const help = document.getElementById("help");
function putText(el, value) {
el.textContent = String(value ?? ""); // null/undefined を "" に
}
function safeHref(el, base, params) {
const qs = new URLSearchParams(params);
el.setAttribute("href", `${base}?${qs}`);
}
const userInput = '<b>太郎</b>'; // 外部データ
putText(message, `ようこそ ${userInput} さん`); // 文字として安全に表示
safeHref(help, "/help", { q: "表示", lang: "ja" }); // 安全にURL生成
</script>
HTMLここが重要です:小さなユーティリティで“常に安全に出力する”癖を標準化すると、プロジェクト全体で事故が激減します。
まとめ
安全な出力の要点は、構造は固定、可変データは“文字”で扱うこと。テキストは textContent/createTextNode/insertAdjacentText、属性は setAttribute/dataset(複合は JSON)、URL は encodeURIComponent/URLSearchParams。boolean属性は有無で管理し、innerHTML は固定テンプレート+全面エスケープに限定。この方針を守れば、初心者でも直感的に書けて、XSSや表示事故を確実に避けられます。

