この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Python標準ライブラリのHTTPサーバーを使い、架空のローカルページでセキュリティヘッダーの役割を比較します。検証ステータス:📘 公式仕様に基づく解説・ブラウザ実表示未確認
HTTPヘッダーを信じていい?セキュリティヘッダーをローカルで観察する
同じHTMLでもブラウザの判断が変わる
WebページのHTMLが同じでも、サーバーが返すHTTPレスポンスヘッダーによって、ブラウザが許可する動作は変わります。例えばContent-Security-Policy(CSP)は、どこからスクリプトなどのリソースを読み込めるかを制限するための仕組みです。画面が表示されたという事実だけでは、セキュリティ上の制約が適切に設定されているか判断できません。
運用担当者が設定を確認するとき、開発者ツールでヘッダーの存在を見るだけでは不十分な場合があります。どのレスポンスに付いているか、意図した制約が有効か、リダイレクトやエラー画面にも必要な設定があるかを分けて考える必要があります。今回は公開サイトを変更せず、自分のPCだけで動くサーバーを使い、HTTPの応答ヘッダーを観察します。
対応環境・実験の前提
| 項目 | 条件 |
|---|---|
| 言語 | Python 3.10以降を想定 |
| 追加パッケージ | 不要。標準ライブラリのみ |
| 接続先 | 127.0.0.1のローカルサーバー |
| 公開範囲 | ローカルループバックのみ |
| 確認対象 | CSPとX-Content-Type-Options |
| 注意 | 実験用のヘッダー値を本番の推奨設定と混同しない |
127.0.0.1は自分のコンピューターへ戻るループバックアドレスです。外部のWebサイトや社内サービスへリクエストを送る必要はありません。ポート番号はこの例では8769を使います。すでに別のアプリが同じポートを使っている場合は変更してください。
小さなHTTPサーバーで応答を作る
下のコードは、HTML本文とともに二つのヘッダーを返します。CSPでは、スクリプトの読み込み元を同じオリジンに制限します。X-Content-Type-Options: nosniffは、ブラウザが特定のリソースについて宣言されたMIMEタイプを無視して別の種類として扱うことを抑制するためのヘッダーです。
from http.server import BaseHTTPRequestHandler, HTTPServer
class DemoHandler(BaseHTTPRequestHandler):
def do_GET(self):
body = b"<h1>Local security header demo</h1>"
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Security-Policy", "default-src 'self'; script-src 'self'")
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 8769), DemoHandler).serve_forever()
実行すると、サーバーはリクエストを待ち受けるため、画面上では処理が終わらずに見えます。別の端末またはブラウザでhttp://127.0.0.1:8769/を開いてください。確認が終わったら実験を実行している端末でCtrl+Cを押して停止します。ブラウザの開発者ツールのNetworkタブから、HTML文書への応答ヘッダーを確認できます。
このコードはインターネットへ公開するサーバーではありません。認証やTLS、アクセス制御、ログ保護を備えた本番用実装ではないので、ループバック以外のアドレスへバインドしないでください。実験用コードを公開Webサーバーへ流用することも避けます。
何を観察すればよいか
ブラウザのNetworkタブでHTML文書の応答を選ぶと、Content-Security-PolicyとX-Content-Type-Optionsという名前のヘッダーが確認できる想定です。ここで重要なのは、ブラウザの画面に表示された見出しと、通信に付いてきた制約情報が別のものだという点です。HTML本文だけをコピーしても、ヘッダーの設定は引き継がれません。
CSPのdefault-src 'self'は、特に個別のディレクティブで上書きされないリソース種別に対して同じオリジンを基本の読み込み元とします。script-src 'self'はスクリプトに対する明示的な指定です。ただし、CSPはXSSなどの問題を完全に解消する万能な対策ではありません。安全なHTML生成、入力の扱い、依存ライブラリ管理と組み合わせる必要があります。
この図は、ページを表示する前に「本文とヘッダーを別々に受け取って判断する」という一点を示しています。
sequenceDiagram participant B as ブラウザ participant S as ローカルHTTPサーバー B->>S: GET / S-->>B: HTTP 200 とCSPヘッダー S-->>B: HTML本文 Note over B: ヘッダーの制約を参照して表示処理
図では応答を説明のため二つに分けていますが、実際のHTTP応答ではヘッダーと本文が同じ応答に含まれます。ブラウザはその応答を解釈し、指定されたポリシーに従ってリソースの読み込み可否を判断します。図の矢印を二つの独立したHTTP通信だと誤解しないでください。
一条件だけ変える:CSPを外す
コードのContent-Security-Policyを設定しているsend_header行だけをコメントアウトして、再起動してみます。今度は応答ヘッダーにCSPが現れないはずです。しかし、HTMLの見出しは同じように表示されます。見た目が変わらないから設定も同じ、という判断が危険であることが分かります。
ここで注意したいのは、CSPを外したからといって直ちに攻撃が成立するわけではないことです。CSPは多層防御の一部であり、攻撃が成立するかどうかはページの実装や入力経路などにも左右されます。比較実験で確認できるのは、ヘッダーが付いている状態と付いていない状態の違いです。実際の防御効果を検証するには、別途、安全に設計したテストケースが必要です。
さらに、CSPはポリシーを厳しくすれば常に良いというものでもありません。既存の外部スクリプトや画像、フォントを読み込むサイトでは、制約が強すぎると正当な機能まで停止します。業務サイトで導入する際は、利用しているリソースの棚卸しと影響評価が欠かせません。
ヘッダーの役割を混同しない
CSPとX-Content-Type-Optionsは目的が違います。前者はリソースの読み込み元などを制約し、後者はMIMEタイプの解釈に関わる挙動を制御します。片方があればもう片方が不要になるわけではありません。さらにHSTSはHTTPS接続を使わせるための別の仕組みで、HTTPのループバック実験へ同じ感覚で追加するものではありません。
また、HTTP応答ヘッダーとHTMLのmeta要素は完全に同じではありません。特定のポリシーはHTTPヘッダーでしか適用できない場合があります。設定ファイルへ文字列を追加しただけで満足せず、最終的にクライアントへ返された応答を観察することが必要です。
失敗・境界条件を切り分ける
ブラウザに接続できないときは、サーバーが起動したままか、ポートが一致しているかを確認します。Address already in useと表示された場合は、同じポートをほかのプロセスが使用している可能性があります。既存プロセスをむやみに停止せず、サンプルのポート番号を未使用のものへ変更します。
ヘッダーが見つからない場合は、NetworkタブでHTML文書のリクエストを選んでいるかを確かめます。画像やfaviconのリクエストを選ぶと別の応答が表示されることがあります。ブラウザのキャッシュやプロキシの影響も切り分け対象ですが、今回のループバック実験ではまず接続先と選択中のリクエストを確認するのが近道です。
CSPを設定したのにスクリプトが動かない場合は、ブラウザのConsoleにポリシー違反が表示されていないか確認します。スクリプトの取得元やインライン実行の有無によって制約に当たる条件が変わります。例外的な許可を無計画に増やすと、導入した防御の効果を弱めることがあります。動かない理由を調べてから、必要なリソースを限定して許可します。
業務サイトで導入するとき
本番ではWebサーバー、リバースプロキシ、CDN、アプリケーションのどこでヘッダーを付与するかを決めます。複数の層で設定すると、意図しないポリシーの組合せや重複が発生する可能性があります。まず最終応答を観察し、設定元を追跡できる状態にしてください。リダイレクトやエラー画面の扱いも運用設計の一部です。
既存サイトへCSPを導入する際は、Content-Security-Policy-Report-Onlyによる影響観察も検討できます。ただし、報告先へ送るデータにはURLなどの情報が含まれる場合があるため、プライバシーや保存期間も考慮します。テスト環境で必要な機能が動くことを確認し、変更手順と切戻し手順を整えてから段階的に適用します。
セキュリティヘッダーは「設定が書いてある」ことより「利用者へ返る応答に適切に付いている」ことが大切です。ブラウザに見えるページの内容と、見えない通信上の制約を分けて確認できれば、障害調査とセキュリティ評価の精度が上がります。
まとめ
今回の実験では、HTML本文を変えずにCSPヘッダーだけを変更することで、画面の見た目とブラウザの制約が別の層にあることを観察できます。設定値の暗記よりも、どの応答に何が付いているかを確認する習慣が役立ちます。実験はローカルだけに留め、本番環境へは影響評価と承認を経て適用してください。
