csp

CSP ツール

実務のContent-Security-Policy: 各ディレクティブが何を管轄するか、source expression と nonce / hash の照合規則、Report-Only での段階導入と壊れやすい箇所を、確認用ツールとあわせて解説します。

1 ツール

§01 分野ガイド

ポリシーの実体

Content-Security-Policy はレスポンスヘッダーで、各ディレクティブが source expression のリストを持ちます。サブリソース取得・インライン実行・フォーム送信・frame埋め込みの前に 参照され、一致がなければリクエストは発生せず違反として報告されます。

照合単位はオリジン(scheme+host+port)で、'self' は同一オリジンのみ。CDNのホストも自身の サブドメインも含みません。強度は最も緩い抜け道で決まり、script-src 'self' 'unsafe-inline' は注入者にscriptタグを渡すのと同義です。CSPヘッダーが複数あれば論理積で合成されます。

ディレクティブ早見表

ディレクティブ管轄default-src継承
script-srcscriptタグ・eval・インラインハンドラする
connect-srcfetch・XHR・WebSocketsendBeaconする
style-src / img-src / font-src / media-srcstyle・画像・font・音声動画する
object-src / frame-src / worker-srcobject・埋め込み文書・workerする
base-uri / form-action / frame-ancestorsbaseタグの値・送信先・埋め込み許可元しない
sandbox / require-trusted-types-for / report-tosandbox・DOM XSS sink・レポート先しない

要点は右列で、default-src 'self' だけでは baseタグ乗っ取り・フォーム持ち出し・ クリックジャッキングが素通りします。

source expression の規則

表記一致するもの注意点
'self' / 'none'文書と同一の scheme+host+port / 何にも一致しないサブドメインは含まない。'none' は他の値と併記した時点で無意味
https:そのschemeの任意のホスト世界中のCDNが許可対象になる
https://cdn.example.comそのオリジン(ポートはscheme既定)path付きで書いてもリダイレクト後は検査されない
*.example.com / *任意の深さのサブドメイン / 任意のホストexample.com 自体は含まない。*data:blob: は含まれない
'nonce-…'一致する nonce 属性を持つ要素128bit以上の乱数を応答ごとに再生成
'sha256-…'バイト列のハッシュが一致するインラインコード空白1文字の差でハッシュが変わる
'strict-dynamic'信頼済みスクリプトが実行時に生成したスクリプト同ディレクティブのhost/scheme指定は無視される
'unsafe-inline'任意のインラインscript / stylenonceかhashが1つでもあると無視される
'unsafe-eval' / 'unsafe-hashes'evalnew Function / onclickstyle 属性へのhash適用限定版が 'wasm-unsafe-eval'

Report-Only での段階導入

候補はまず Content-Security-Policy-Report-Only で配信します。評価は同一でブロックせず、 強制ポリシーと並行配信できます。収集口は report-urireport-to(別途 Reporting-Endpoints が必要)。レポート送信は connect-src の対象外です。 クロスオリジンの blocked-uri はオリジンまで丸められるため、'report-sample' を足し effective-directive で集計します。

ツールの使い分け

稼働中サイトの論点は「実際に配信されているポリシー」。HTTPヘッダーapi.sitekits.dev 経由でステータス・リダイレクト数・全ヘッダーを返します(本文は取得 せず、内部アドレスはSSRF拒否)。CDNの書き換えもここで分かります。これから書くなら CSPジェネレーター。ディレクティブ欄14個に加えて既定でONの upgrade-insecure-requests チェックボックスがあり、初期値は default-src 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none' の堅めのベースライン。 つまり何も触らなくても、これに upgrade-insecure-requests が付いたヘッダーが出ます。 組み立ては入力のたびにブラウザ内で再構築。欄にない report-tosandboxrequire-trusted-types-for は手で追記します。

許可リスト作りは棚卸しです。HARを HARビューアー で一覧し、不明なURLを URLパーサー で scheme+host+port に落とし、共有前に HARサニタイザー を通します(Cookieや Authorization を含むため)。 差分は テキスト比較csp-reportJSONフォーマッターREST APIテスター は ブラウザから対象へ直接送信し(sitekits非経由)、適用されるのは自サイトではなくこのツール ページのポリシー(connect-src'self' https: に緩和)です。ここで通るのに自サイトで 失敗するなら、疑うべきは自分のヘッダー。ただし原因の特定まではできません。connect-src によるブロックもCORS拒否も fetch の TypeError になり、本ツールの表示は両方をまとめた 1種類のメッセージだけです。関連: HTTPハブセキュリティハブセキュリティ職向け

よくある破損パターン

nonce導入で 'unsafe-inline' が失効する

自前でscriptタグを注入するタグマネージャーやウィジェットが止まります。解は 'strict-dynamic'

hashは16進ではなくbase64

ハッシュ生成 の16進出力を Base64 に通しても別値です。 値はコンソールエラーから取ります。

固定値のnonceは 'unsafe-inline' と同義

応答ごとの再生成が必須で、テンプレート即値やHTMLのみキャッシュは推測可能です。 UUIDジェネレーター は手動確認用まで。

metaタグではCSPの半分が表現できない

frame-ancestorssandboxreport-uri・Report-Only は meta http-equiv で無視され、 効くのはそのタグ以降だけです。

FAQ
script-src に 'unsafe-inline' を入れたのにインラインスクリプトがブロックされるのはなぜ?
同じディレクティブに nonce か hash が含まれているためです。'nonce-…' や 'sha256-…' が1つでもあると、ブラウザは仕様どおり 'unsafe-inline' を無視します。対象のインラインスクリプトにその応答のnonceを付ける、hashを追加する、あるいは 'strict-dynamic' を足して信頼済みスクリプトが生成したスクリプトに信頼を継承させてください。
default-src を書けば全ディレクティブをカバーできますか?
できません。厳しく書いたポリシーで最も多い穴がここです。base-uri / form-action / frame-ancestors / sandbox / report-uri / report-to は default-src にフォールバックしないため、default-src 'self' だけでは base タグの乗っ取り、任意ホストへのフォーム送信、任意サイトからのframe埋め込みが通ります。これらは個別に明示してください。
本番を壊さずにCSPを導入する手順は?
まず Content-Security-Policy-Report-Only として配信します。評価は本番と同一でブロックだけ行わず、強制ポリシーと並行して送れます。1〜2週間レポートを集めてからヘッダー名を切り替えます。ブラウザ拡張がインラインスクリプトを注入するため、対処不能なノイズが相当量混ざる前提で見てください。
インラインスクリプトには nonce と hash のどちらを使うべき?
応答ごとにHTMLを生成するなら nonce です。値は毎回予測不可能で新しくなければ意味がありません。静的HTMLやCDNキャッシュ前提なら hash が向きます。サーバー側の乱数が不要な代わり、インラインブロックのバイト列そのものが対象なので空白1文字の変更で無効になります。'strict-dynamic' はどちらとも併用でき、そのディレクティブが既に許可したスクリプトから信頼を伝播させます。script-src 'sha256-…' 'strict-dynamic' https: 'unsafe-inline' はまさにこの静的HTML向けの定番構成です。信頼が及ぶのはそのスクリプトが実行時に生成した分だけで、マークアップに直書きしたscriptタグには及びません。
違反レポートの blocked-uri にファイル名が出ず、ドメインだけになるのはなぜ?
クロスオリジンの違反は仕様上オリジンまで丸められるためです。完全なURLを報告すると、CSPが読ませなかった情報をページ側に渡してしまうからで、分かるのは拒否されたホストであってリソースではありません。リダイレクトを挟んだ場合は最初のオリジンしか残らないこともあります。該当ディレクティブに 'report-sample' を足せばレポートに問題のコード片が短く含まれ、effective-directive で集計すればどのルールが発火しているかが分かります。同一オリジンの違反は丸められないので、そちらはパスまで残ります。