Cloudflare Pagesの_headersとTransform Rulesで実装するWebセキュリティヘッダー設計入門
静的サイトジェネレーター(Astro や Next.js 等)で構築したサイトは、サーバーサイドのデータベースを持たないため SQL インジェクション等のリスクはありません。しかし、ブラウザ側で実行されるクロスサイトスクリプティング(XSS)、クリックジャッキング、MIMEタイプスニッフィングといったクライアントサイドの脅威に対しては、適切な HTTP セキュリティレスポンスヘッダー を付与して防御する必要があります。
本記事では、各セキュリティヘッダーが防ぐ攻撃の仕組みと、Cloudflare Pages の public/_headers ファイル を使ってコードベースで一元管理する実践的な設定方法を解説します。
1. 導入すべき主要セキュリティヘッダーと役割一覧
まずは、一般的な技術ブログやWebメディアにおいて標準で導入すべき5つのHTTPレスポンスヘッダーを整理します。
| ヘッダー名 | 推奨設定値の例 | 防御できる主なリスク・役割 |
|---|---|---|
X-Content-Type-Options | nosniff | ブラウザが Content-Type を無視してファイル内容を推測(MIMEスニッフィング)し、画像等を装ったスクリプトを実行するのを防ぐ |
X-Frame-Options | SAMEORIGIN(または DENY) | 外部の悪意あるサイトが透明な <iframe> で自サイトを読み込み、ユーザーに意図しないボタンを押させる「クリックジャッキング」を防ぐ |
Referrer-Policy | strict-origin-when-cross-origin | 外部サイトへ遷移する際、URLのパスやクエリパラメータを伏せ、オリジン(ドメイン名)のみをリファラとして送信する |
Permissions-Policy | camera=(), microphone=(), geolocation=() | サイト内で使用しないブラウザAPI(カメラ・マイク・位置情報など)へのアクセス権限を明示的に無効化する |
Strict-Transport-Security | max-age=31536000; includeSubDomains | ブラウザに対して常にHTTPS通信のみを使用するよう強制する(HSTS) |
2. Cloudflare Pages の public/_headers で設定する方法
Cloudflare Pages では、プロジェクトの public/_headers(ビルド後に dist/_headers として出力されるファイル)にヘッダー定義を記述しておくだけで、オリジンサーバーやエッジの追加設定なしで全レスポンスにカスタムヘッダーを付与できます。
2.1 public/_headers の具体的な記述例
Astro プロジェクトの public/_headers に以下の内容を記述します。
/*
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()
/_astro/*
Cache-Control: public, max-age=31536000, immutable
2.2 アセットキャッシュの最適化も同時に行う
上記の後半にある /_astro/* の指定は、Astro がビルド時にファイル名へハッシュ値を付与した CSS・JS・画像アセットに対して、1年間(31,536,000秒)の長期ブラウザキャッシュ(immutable) を指示するものです。
HTML ファイル自体は常に最新を取得させつつ、ハッシュ付きの静的アセットだけを強力にキャッシュさせることで、2ページ目以降の画面遷移が瞬時に完了するようになります。
3. Google AdSense や Google Analytics 利用時の CSP(Content-Security-Policy)の注意点
セキュリティヘッダーのなかでも特に強力なのが Content-Security-Policy(CSP)ですが、Google AdSense や Google Analytics 4(GA4)を導入するサイトで厳格すぎる CSP を設定すると、広告スクリプトや計測ビーコンがブロックされて審査落ちや計測不能の原因になります。
3.1 AdSense が動的に読み込む外部ドメインの特性
Google AdSense のスクリプト(adsbygoogle.js)は、pagead2.googlesyndication.com だけでなく、広告配信最適化や不正クリック検知のために以下のような多数のドメインと通信を行います。
*.googlesyndication.com*.googleadservices.com*.doubleclick.net*.google.com/*.gstatic.com*.adtrafficquality.google
そのため、AdSense を利用するサイトで CSP を設定する場合は、script-src や frame-src を特定のホスト名だけで狭く縛るのではなく、まずは frame-ancestors 'self'(自サイト以外からの iframe 埋め込み禁止)を中心に適用するか、事前に Content-Security-Policy-Report-Only ヘッダーを使ってブラウザコンソール上でブロックが発生しないか十分に検証してから本番適用するのが鉄則です。
# 広告や外部連携を壊さずにクリックジャッキング防止とHTTPSアップグレードを行う安全なCSP構成例
/*
Content-Security-Policy: upgrade-insecure-requests; frame-ancestors 'self';
4. Cloudflare ダッシュボード(Transform Rules)での設定との使い分け
Cloudflare には public/_headers のほかに、ダッシュボード上の 「ルール」>「変換ルール(Transform Rules)」>「レスポンス ヘッダーの変更」 機能もあります。両者は以下のように使い分けるのがおすすめです。
- Git リポジトリで履歴管理したいサイト固有のヘッダー:
public/_headersに記述する(レビューやロールバックが容易)。 - サブドメイン全体や Pages 以外の Worker / R2 バケットにも一括適用したいヘッダー:Cloudflare の Transform Rules(Managed Transforms の「セキュリティヘッダーを追加」機能を含む)を利用する。
注意:ヘッダーの二重付与を避ける
public/_headersと Cloudflare ダッシュボードの「Managed Transforms」の両方で同じヘッダー(例:X-Content-Type-Options)を有効にすると、レスポンスヘッダーが重複して出力される場合があります。設定後は必ずcurl -I https://egpl.net/で実際のレスポンスヘッダーを確認しましょう。
5. 設定後の動作確認コマンド
デプロイ完了後、ターミナルから以下のコマンドを実行して、意図したとおりにセキュリティヘッダーが返ってきているか検証します。
curl -s -D - -o /dev/null https://egpl.net/
出力結果に以下のような行が含まれていれば、正常に適用されています。
HTTP/2 200
content-type: text/html; charset=utf-8
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
permissions-policy: accelerometer=(), camera=(), geolocation=(), ...
まとめ
public/_headers を活用すれば、わずか十数行のテキストファイルを追加するだけで、セキュリティ基準のクリアと静的アセットのキャッシュ高速化を同時に実現できます。特に広告配信やアクセス解析を併用するサイトでは、外部スクリプトの動作を妨げないバランスの取れたヘッダー設計を心がけましょう。