
Content Security Policy(CSP)入門 - XSS を緩和するブラウザの防御層を設計する
読みやすいコードの原則。全エンジニアの基礎。
通称「徳丸本」。Webセキュリティの定番教科書。
打鍵感で選ぶ定番キーボード。長時間の実装に。
当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。
XSS(クロスサイトスクリプティング)対策というと、まず出力エスケープやサニタイズを思い浮かべます。ですが、テンプレートのどこか一箇所でエスケープを漏らせば攻撃は成立します。そこで「万一スクリプトが注入されても、ブラウザ側でその実行を止める」二重の防御層を用意するのが Content Security Policy(CSP) です。
この記事では、CSP の仕組みと実務での設計方針を、MDN・W3C CSP Level 3・web.dev を一次ソースに整理します。同じくブラウザのセキュリティ機構である CORS の仕組み と併せて押さえると、Web セキュリティの土台が固まります。
CSP とは何か、何を緩和するか
CSP は「このページでは、どこから読み込んだリソースを、どの種類だけ許可するか」をブラウザに宣言する仕組みです。サーバが HTTP レスポンスヘッダでポリシーを送り、ブラウザがそれに反するリソースの読み込みやスクリプト実行をブロックします。
主眼は XSS の緩和です。攻撃者がページに <script> を注入できたとしても、CSP が「許可されていないスクリプトは実行しない」と決めていれば、注入されたコードは動きません。ほかにも次を緩和できます。
- インラインスクリプトやインラインイベントハンドラの実行を止める
frame-ancestorsで他サイトへの埋め込み(クリックジャッキング)を制限するupgrade-insecure-requestsで混在コンテンツ(HTTP 読み込み)を抑える
IMPORTANT
CSP は XSS を「根絶」する仕組みではなく、被害を緩和する多層防御の一層です。第一の対策はあくまで入力検証と出力エスケープです。実際に React などでも CVE-2025-55182 のようなインジェクション由来の脆弱性 が起こり得ます。CSP はそうした「漏れ」が起きたときの最後の緩衝材として設計します。
基本の書き方(ヘッダとディレクティブ)
CSP は Content-Security-Policy レスポンスヘッダで送るのが基本です。値はディレクティブをセミコロンで区切って並べます。
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'これは「原則すべて自オリジンのみ、画像は自オリジンと data URI を許可、<object> は全面禁止」という意味です。各ディレクティブは「名前 + ソースリスト」で構成されます。
指定手段は3つあります。
Content-Security-Policyヘッダ: 標準。すべてのディレクティブが使え、これが推奨Content-Security-Policy-Report-Onlyヘッダ: ポリシーを強制せず違反レポートだけ送る。段階導入に使う<meta http-equiv>タグ: HTML 内で指定。ただし対応しないディレクティブがあり(frame-ancestorsやreport-uriなど)、Report-Only も使えない。SPA など限定的な用途向け
NOTE
ヘッダはメインの HTML だけでなく、原則すべてのレスポンスに付けるのが MDN の推奨です。CSP は文書ごとに適用されるため、各 HTML 応答に確実に付与する運用にします。
主要ディレクティブ早見
ディレクティブは大きく「リソース取得を制御するもの」と「文書全体の挙動を制御するもの」に分かれます。取得系は原則、指定がなければ default-src の値にフォールバックします。
| ディレクティブ | 制御対象 |
|---|---|
default-src | 未指定の取得系ディレクティブのフォールバック |
script-src | JavaScript の読み込みと実行 |
style-src | CSS スタイルシート |
img-src | 画像 |
connect-src | fetch / XMLHttpRequest / WebSocket / EventSource の接続先 |
font-src | Web フォント |
frame-src | 埋め込む <iframe> の読み込み元 |
object-src | <object> / <embed>(プラグイン) |
media-src | 音声・動画 |
manifest-src | Web アプリマニフェスト |
文書全体の挙動を制御する代表格は次のとおりです。
| ディレクティブ | 役割 |
|---|---|
frame-ancestors | このページを埋め込める側を制限(クリックジャッキング対策) |
base-uri | <base> 要素で設定できる URL を制限 |
form-action | フォームの送信先を制限 |
upgrade-insecure-requests | HTTP のサブリソースを自動的に HTTPS へ格上げ |
report-to / report-uri | 違反レポートの送信先 |
NOTE
frame-ancestors は、古い X-Frame-Options ヘッダの上位互換にあたる制御です。複数のオリジンを許可でき、より柔軟にクリックジャッキング対策ができます。
ソースキーワードと 'unsafe-inline' の危険
ソースリストには、ホスト名(example.com、*.example.org)やスキーム(https:、data:)のほか、次のキーワードが使えます。
'self': 文書と同一オリジン'none': 一切許可しない'unsafe-inline': インラインの<script>/ スタイルやイベントハンドラを許可'unsafe-eval':eval()やFunction()、文字列を渡すsetTimeoutなどを許可
script-src(または default-src)が指定されると、既定ではインラインスクリプトが全面的にブロックされます。具体的には次が動かなくなります。
- インラインの
<script>ブロック onclick=などのインラインイベントハンドラ属性javascript:URL
これを一括で復活させるのが 'unsafe-inline' ですが、これを付けた瞬間 CSP の XSS 防御はほぼ無力化します。攻撃者が注入する XSS ペイロードもインラインスクリプトだからです。同様に 'unsafe-eval' も eval 系を通してしまうため、原則避けます。
WARNING
「CSP を入れたら既存のインラインスクリプトが全部止まった」からといって、安易に 'unsafe-inline' で逃げてはいけません。それは鍵をかけた玄関を開けっぱなしにするのと同じです。次に述べる nonce か hash で、必要なスクリプトだけを個別に許可します。
nonce と hash による strict CSP
ホスト名の許可リスト(allowlist)方式の CSP、たとえば script-src https://apis.example.com は運用が難しく、かつバイパスされやすいことが Google の研究で示されています。許可したドメイン上に JSONP エンドポイントや古いライブラリがあると、そこを踏み台にポリシーを回避されるためです。
そこで web.dev が推奨するのが、nonce または hash を使った strict CSP です。URL ではなく「このスクリプトは自分が意図したものだ」という暗号的な印で許可します。
nonce 方式(動的なページ向け)
nonce は「1回だけ使うランダム値」です。サーバがレスポンスごとに新しいランダム値を生成し、CSP ヘッダと <script> タグの両方に同じ値を書きます。攻撃者はその時々の nonce を知り得ないため、注入したスクリプトを実行できません。
Content-Security-Policy: script-src 'nonce-r4nd0m123' 'strict-dynamic'; object-src 'none'; base-uri 'none'<script nonce="r4nd0m123" src="/app.js"></script>
<script nonce="r4nd0m123">console.log('hello');</script>nonce の条件は、128 ビット以上の暗号論的乱数で、レスポンスごとに再生成することです。固定値の使い回しは意味がありません。サーバ側では crypto.randomUUID() などで毎回生成します。
hash 方式(静的なページ向け)
サーバでテンプレートに nonce を差し込めない静的配信では、スクリプト内容の SHA ハッシュを許可リストに書きます。インラインスクリプトの中身から sha256- などのハッシュを計算し、CSP に列挙します。
Content-Security-Policy: script-src 'sha256-cd9827ad...'; object-src 'none'; base-uri 'none'外部スクリプトを hash で許可する場合は、<script> 側に同じ値の integrity 属性を付けます。内容が1バイトでも変わればハッシュが変わるため、更新のたびに再計算が必要です。
strict-dynamic の役割
'strict-dynamic' は、「nonce や hash で信頼したスクリプトが動的に読み込む子スクリプトも信頼する」というキーワードです。信頼したローダーが document.createElement('script') で追加するスクリプトに、いちいち nonce を付けなくてよくなります。これにより、多くのサードパーティライブラリを strict CSP と共存させやすくなります。
strict CSP の要点は、script-src の堅牢化に加えて object-src 'none'(プラグイン悪用の遮断)と base-uri 'none'(注入された <base> によるスクリプト読み込み元の乗っ取り防止)を必ずセットにすることです。
レポートによる段階導入(Report-Only)
いきなり本番で強制すると、正規のスクリプトまで止めてサイトを壊しかねません。そこで Content-Security-Policy-Report-Only で「ブロックはせず違反だけ通知」する段階を挟みます。
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-to csp-endpointレポート送信先は report-to ディレクティブで指定し、実際のエンドポイントは Reporting-Endpoints ヘッダで定義します(Reporting API)。
Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports"
Content-Security-Policy: default-src 'self'; report-to csp-endpoint違反が起きると、ブラウザが JSON のレポートを POST します(effectiveDirective や blockedURL などが入ります)。導入の流れは次のとおりです。
- Report-Only でポリシーを流し、違反レポートを収集する
- 正規の違反(直すべき自サイトの実装)と、無視してよいノイズを仕分ける
- ポリシーを調整し、問題がなくなったら
Content-Security-Policyで強制に切り替える
なお、強制用とレポート専用の両方のヘッダを同時に出すこともでき、強制しつつ次の厳しいポリシーを Report-Only で試す運用が可能です。
NOTE
W3C の CSP Level 3(執筆時点で 2026年5月付の W3C Working Draft)では、report-uri は report-to へ置き換える形で非推奨(deprecated)と明記されています。ただし report-to はブラウザ対応や送信先定義の都合があるため、当面は両方を併記する運用も見られます。最新の対応状況は MDN で確認してください(この点は「要確認」です)。
よくある落とし穴
- インラインスクリプト/イベントハンドラが全部止まる:
script-srcを入れた時点でインラインは既定でブロック。onclick=はイベントリスナーへ、インライン<script>は nonce か hash へ移行する - 安易な
'unsafe-inline': 動かすためだけに付けると XSS 防御が実質ゼロになる。'unsafe-inline'は nonce/hash があると無視されるため、モダンブラウザ向けには nonce と併記して後方互換に使う手もある - ホスト許可リストへの過信:
script-srcにドメインを並べる方式はバイパスされやすく、保守も破綻しがち。nonce/hash ベースへ寄せる - サードパーティ埋め込みの見落とし: 解析タグや広告、フォント CDN、
connect-srcで通信する外部 API など、実際に使う読み込み元をレポートで洗い出してから強制する base-uriとobject-srcの付け忘れ: strict CSP でも、この2つを'none'にしないと抜け道が残る- いきなり強制: Report-Only を挟まず本番強制して、正規機能ごと止める事故が定番
これらは、認証まわりで Cookie・セッション・SameSite を「まず Lax で観測してから締める」のと同じく、観測してから締めるのが安全という点で共通します。
まとめ
- CSP は「どこから読んだ何を許可するか」をブラウザに宣言する仕組み。主眼は XSS の緩和だが、根絶ではなく多層防御の一層
- 指定は
Content-Security-Policyヘッダが基本。段階導入にはContent-Security-Policy-Report-Only、限定用途で<meta>も可 - 取得系ディレクティブは
default-srcにフォールバック。frame-ancestorsはクリックジャッキング対策、base-uri/form-action/object-srcも要 'unsafe-inline'/'unsafe-eval'は原則使わない。必要なスクリプトは nonce か hash で個別許可する- モダンな指針は nonce +
'strict-dynamic'+object-src 'none'+base-uri 'none'の strict CSP。ホスト許可リスト方式はバイパスされやすい - 導入は Report-Only で観測 → 調整 → 強制の順。レポートは
report-toと Reporting API で受ける
CSP は一度に完璧を目指すと必ず折れます。まずは Report-Only で自サイトの実態を知り、少しずつ締めていく。その運用設計こそが、CSP を機能させる最大のコツです。


