「静的サイトだからHTTPセキュリティヘッダーは不要」。Webサイトのセキュリティ診断で、こういう判断をよく見ます。けれどこれは、半分くらい正しくて、半分くらい誤りです。
HTTPヘッダーが提供する防御層 (clickjacking抑止、MIMEスニッフィング抑止、リファラ漏れ制御、不要機能のdeny、HTTPS強制) は、サイトがSSGであろうとSSRであろうと等しく必要。サーバー側 (Express、Astroミドルウェア、Edge function) で設定する代わりに、Cloudflare Pagesなら public/_headers ファイル1つで完結します。
SSGだからヘッダー不要、という誤解
「静的サイトにAPIはないからXSSリスクは低い」「DBがないから情報漏えいリスクは低い」。半分は正しい。けれどHTTPヘッダーが守るのは、アプリケーションロジックではなく、ブラウザの解釈そのものです。
<iframe> で自サイトを埋め込まれて clickjacking。Content-Typeが曖昧なファイルをブラウザがJavaScriptとして解釈。外部サイトへの遷移時に Referer ヘッダーで内部URLが漏れる。HTTPSとHTTPを混在してダウングレード攻撃を受ける。いずれもSSG、SPA、SSRの区別なく起きます。アプリケーションコードでは防げない、ヘッダーでしか塞げない領域です。
まず入れる5つのヘッダー
HSTS、X-Content-Type-Options、Referrer-Policy、Permissions-Policy、X-Frame-Options。この5つを先に入れます。
Strict-Transport-Security (HSTS)
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
ブラウザに「このドメインは今後2年間、必ずHTTPSで接続しろ」と伝えます。includeSubDomains でサブドメインもHTTPS強制。サブドメインに HTTP-only のサービス (社内ツール、レガシー管理画面等) が残っていると、その時点でアクセス不能になります。企業サイトでは事前のサブドメイン棚卸しが必須。preload はHSTS Preload Listへの登録シグナル (登録は別途 hstspreload.org で申請)。
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
ブラウザのMIME type推測を無効化します。Content-Typeヘッダーが宣言したタイプをブラウザに信じさせる。これがないと、CSSや画像として配信したファイルが状況によってJavaScriptとして実行される攻撃 (sniffing attack) の道が残ります。
Referrer-Policy: strict-origin-when-cross-origin
Referrer-Policy: strict-origin-when-cross-origin
クロスオリジン遷移時にはorigin (https://example.com) だけを送り、パスやクエリは送らない。同origin内では完全なURLを送ります。最近のブラウザのセキュアデフォルト。
Permissions-Policy
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
ブラウザの機能を明示的にdeny。() (空のorigin一覧) は「どこからも使えない」の意。コーポレートサイトに camera、mic、geolocation はまず不要です。interest-cohort=() はFLoC opt-out (deprecatedですが残しても害なし)。
X-Frame-Options: DENY
X-Frame-Options: DENY
自サイトを <iframe> 内に埋め込み禁止 (clickjacking抑止)。CSPの frame-ancestors 'none' でも同等の制御ができますが、古いブラウザ対応としてX-Frame-Optionsも併設します。
_headersに書く
Cloudflare PagesはNetlifyと同じ _headers ファイル形式をサポートします。public/_headers に書けば、ビルド時に dist/_headers に複製され、配信時にこのルールが適用されます。
/*
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
X-Frame-Options: DENY
/* は全パスに適用。特定パスだけ override したい場合は /admin/* のようなパターンで別ルールを書きます。デプロイ後は curl -I https://example.com/ でヘッダーが返るか確認。
CSPはReport-Onlyから段階導入する
Content-Security-Policy (CSP) は最も強力な防御層。一方で、設定ミスで「ページの一部が動かない」事故を起こしやすい。最近のビルドツールはインラインスクリプトやインラインスタイルを必要に応じて生成するため、'self' だけだと拒否される可能性があります。
そこで Content-Security-Policy-Report-Only で違反レポートをログ収集してから enforce 切替する2段階構成を組みます。
Phase A: Report-Onlyで検証
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://api.web3forms.com; form-action 'self' https://api.web3forms.com; frame-ancestors 'none'; base-uri 'self'; report-uri https://example.report-uri.com/r/d/csp/reportOnly; report-to default;
Reporting-Endpoints: default="https://example.report-uri.com/r/d/csp/reportOnly"
レポート送信先は、新規構築なら Reporting-Endpoints (現行のレポーティング API) を使い、互換のために CSP に report-uri も併記しておく構成が無難です。MDN 上では Report-To ヘッダーは deprecated 扱い。
Report-Onlyはブロックせずレポートだけ送るモード。1〜2週間運用して違反0件を確認します。
Phase B: enforce切替
違反が0件になったら、ヘッダー名を Content-Security-Policy (Report-Onlyではない) に変えて enforce 開始。
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; ...
CSPディレクティブの例
script-src 'self' で自サイトのJavaScriptのみ、インライン不可。style-src 'self' 'unsafe-inline' で <style> 直書きや要素の style= 属性が出る場合に 'unsafe-inline' を許容 (Tailwindのような Atomic CSS フレームワーク自体は外部 CSS として生成されるので、'unsafe-inline' が必要かは Astro やビルド成果物に何が含まれるか次第)。connect-src 'self' https://api.web3forms.com でXHRやfetchの宛先を制限。form-action 'self' https://api.web3forms.com でform POST先を制限。frame-ancestors 'none' でiframe埋め込み禁止。base-uri 'self' で <base> タグのoriginを制限。
HSTS preload登録は本番運用が安定してから
Strict-Transport-Security に preload ディレクティブを入れただけでは、HSTS Preload Listに自動登録されません。 hstspreload.org で申請する別ステップが要ります。
申請の前提条件: max-age >= 31536000 (1年以上)。includeSubDomains あり。preload ディレクティブあり。HTTPSのみで配信、HTTP から HTTPS のリダイレクト設定済み。すべてのサブドメインがHTTPSに対応。
preload list 登録後の解除は数ヶ月単位かかります。サブドメインでHTTP-onlyのサービスを残している間は preload しない。本番運用が安定してから登録します。
静的サイトでもHTTPセキュリティヘッダーは必要です。最低限 HSTS、nosniff、Referrer-Policy、Permissions-Policy、X-Frame-Options の 5 つを _headers に入れ、CSP は Report-Only から enforce で段階導入する。これだけで、サーバーフレームワークを介さずにアプリケーションの外側で防御層を整えられます。