Astroサイトのセキュリティヘッダーを確認する―CSP・HSTSで守れる範囲と設定ミスの影響
HTTPセキュリティヘッダーは、ブラウザに読み込み元やHTTPS、iframe、MIME typeなどの制限を伝える仕組みです。Astro+Cloudflare Workers Static Assetsでの設定場所、CSP・HSTSの役割、GA4などを壊す設定ミス、過去の大規模事故から見える限界まで確認します。

WebサイトをHTTPSで公開していても、ブラウザ側で許可する動作はHTTPSだけでは決まらない。
外部のJavaScriptをどこから読み込めるか。
別サイトのiframeへページを埋め込めるか。
一度HTTPSで開いたドメインを、次回からHTTPで開かせないか。
レスポンスのContent-Typeをブラウザが勝手に推測してよいか。
こうした制御をブラウザへ伝えるのがHTTPセキュリティヘッダーになる。
ゆまラボは、Astroで生成した静的HTMLをCloudflare WorkersのStatic Assetsから配信する構成で始めた。その後、bringain.comを独自ドメインとしてWorkersへ接続し、HTTPSで公開している。
この構成ではWordPressのようにWebサーバー設定を直接変更する必要はない。
一方で、AstroがHTMLを生成しただけではContent-Security-PolicyやStrict-Transport-Securityが自動的に十分な値で付くわけでもない。
Cloudflare Workers Static Assetsならpublic/_headersが使える。
ただし、セキュリティヘッダーは「付ければ多いほど安全」という設定ではない。特にCSPとHSTSは、設定を強くしすぎると正常なサイト機能まで止める。
ゆまラボではGoogle Analyticsも使っているため、CSPを適当に'self'だけで始めるとアクセス解析そのものを壊す可能性がある。
HTTPレスポンスヘッダーはHTMLの外側で効く#
ブラウザがページを開くと、HTML本文だけでなくHTTPレスポンスヘッダーも受け取る。
簡略化すると次の形。
HTTP/2 200
Content-Type: text/html; charset=UTF-8
Content-Security-Policy: ...
Strict-Transport-Security: ...
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
<!doctype html>
<html>
...
HTMLへ、
<meta name="...">
を追加するのとは役割が違う。
Strict-Transport-SecurityのようにHTTPヘッダーでなければ機能しないものがある。Clickjacking対策に使うX-Frame-Optionsも<meta>へ書いても効かない。
CSPは一部を<meta http-equiv>で指定できるが、frame-ancestorsなどmetaでは使えない指示もある。
配信時のレスポンスへ設定する方が、サイト全体へ一貫して適用しやすい。

※ 記事内の図は内容を整理するために作成した概念図です。各社の公式資料や実際の製品画面ではありません。
先に見るならCSP・HSTS・nosniff・frame制御#
セキュリティ関連のレスポンスヘッダーは多い。
小規模な静的ブログで最初から全部を追加するより、何を防ぎたいかを分けた方が扱いやすい。
Content-Security-Policy#
CSPは、ページが読み込めるスクリプト、画像、CSS、font、iframe、通信先などをブラウザ側で制限する。
例えば非常に単純化すると、
Content-Security-Policy: default-src 'self'
なら、基本的な取得元を同一originへ限定する。
さらに、
script-src
style-src
img-src
connect-src
frame-src
frame-ancestors
form-action
などを分けて指定できる。
XSSや第三者スクリプト侵害が起きた場合でも、攻撃コードが自由に外部リソースを読み込んだり、任意の送信先へ通信したりする範囲を狭められる。
ただしCSPはXSSそのものを消す仕組みではない。
OWASPも、CSPをXSS対策の追加レイヤーとして扱い、出力エンコードやsanitizeなどの基本対策を置き換えないよう案内している。
Strict-Transport-Security#
HSTSは、一度HTTPSで受け取ったブラウザに対して、そのドメインへ今後HTTPで接続せずHTTPSを使うよう指示する。
Strict-Transport-Security: max-age=31536000
のように有効期間を指定する。
HTTPからHTTPSへのリダイレクトだけでは、最初のHTTPアクセスが発生する余地がある。HSTSを記憶したブラウザではHTTP接続そのものを避けられる。
CloudflareもHSTSをHTTPS downgrade attackへの対策として案内している。
X-Content-Type-Options#
X-Content-Type-Options: nosniff
は、サーバーが返したMIME typeをブラウザが別の種類として推測するMIME sniffingを抑止する。
特にscriptやstylesheetでは、想定外のMIME typeを実行対象として扱わせないための追加防御になる。
iframeへの埋め込み制御#
古くから使われるのは、
X-Frame-Options: DENY
または、
X-Frame-Options: SAMEORIGIN
になる。
現在はCSPの、
Content-Security-Policy: frame-ancestors 'none'
の方が細かく指定できる。
他サイトから透明なiframeを重ね、利用者に別のボタンを押しているように見せるClickjackingへの対策になる。
OWASPは対応ブラウザではCSP frame-ancestorsを推奨し、X-Frame-Optionsを旧ブラウザ向けの防御として説明している。
Astro+Cloudflare Workers Static Assetsならpublic/_headersで設定できる#
Cloudflare WorkersのStatic Assetsでは、_headersというテキストファイルを使ってカスタムレスポンスヘッダーを指定できる。
AstroのCloudflare向け公式ドキュメントでも、
public/_headers
へ置けばビルド成果物へコピーされるとしている。
ゆまラボの現在のように静的HTMLをStatic Assetsから配信する構成なら、まずここが候補になる。
例えばCSPをまだ強制しない段階なら、次のような設定から各ヘッダーの影響を確認できる。
/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: DENY
Permissions-Policy: camera=(), microphone=(), geolocation=()
HSTSはHTTPS運用を確認したうえで別に追加する。
/*
Strict-Transport-Security: max-age=31536000
includeSubDomainsやpreloadまで最初から付けるかは別判断になる。
CSPも、いきなり強制せず、
Content-Security-Policy-Report-Only: ...
で違反候補を確認してから本番のContent-Security-Policyへ移す方法がある。
Cloudflare自身もCSPのreport-onlyヘッダーをクライアント側セキュリティの監視に利用している。
SSRやWorker生成レスポンスでは同じ方法にならない#
ここはAstroで注意したい。
Cloudflareの_headersはStatic Assetsへ適用される。
Workerコードが生成したレスポンスには適用されない。
AstroでCloudflare adapterを使い、SSRやon-demand renderingへ変更した場合は、WorkerのResponseやAstro middleware側でヘッダーを付ける必要がある。
現在の構成が静的でも、将来API routeやサーバー側機能を追加したら「public/_headersを書いたからサイト全体で同じ」とは限らない。
設定場所はAstroそのものより、最終的にそのレスポンスを誰が返しているかで決まる。
CSPは強くするとGoogle Analyticsまで止める#
セキュリティヘッダーのデメリットが最も出やすいのはCSP。
ゆまラボではGoogle AnalyticsをAstroへ追加している。
GA4は自サイトだけで完結せず、外部ドメインからJavaScriptを読み込み、計測データを外部へ送信する。
この状態で、
Content-Security-Policy: default-src 'self'
だけを強制すると、外部scriptや通信先がCSP違反になり、GA4が動かなくなる。
将来Google AdSenseを追加すれば、許可が必要なscript、frame、画像、通信先はさらに増える。
CSPでは次のような事故が起こりやすい。
CSPを強化
↓
ページ自体は表示される
↓
アクセス解析だけ止まる
または、
CSPを強化
↓
外部fontや画像が拒否される
↓
一部デザインだけ崩れる
サイトが真っ白にならなくても、裏側で機能が失敗することがある。
本番へ適用するときはDevToolsのConsoleとNetworkを見る。
CSP違反が出ているなら、必要な通信なのか、不必要な外部アクセスなのかを分ける。
「エラーが出たドメインを全部allowlistへ追加する」という直し方をすると、CSPを置いた意味が薄くなる。
HSTSは設定を戻しにくい#
HSTSも、値を付けるだけなら簡単。
問題はブラウザが設定を一定期間記憶すること。
例えば長いmax-ageを設定したあと、一時的にHTTPSを使えない構成へ戻しても、ブラウザはHTTPへ接続しない。
includeSubDomainsを付けると、まだHTTPS化していないサブドメインまで対象になる。
さらにpreloadを使う場合は、通常のレスポンスヘッダーより影響が長くなる。
CloudflareのHSTS設定画面でも、有効化前にHTTPSを維持できることなどの要件確認を求めている。
ゆまラボは現在bringain.comをHTTPSで公開しているが、それだけで即preloadまで有効にする理由にはならない。
HSTSは、
HTTPSが常時利用できる
↓
影響するサブドメインを確認
↓
max-ageを設定
↓
必要ならincludeSubDomainsを検討
↓
preloadはさらに別判断
くらいに分けた方が戻しやすい。
Referrer-PolicyやPermissions-Policyも副作用がある#
Referrer-Policyは、別ページへ移動するときに送るReferer情報の範囲を制御する。
OWASPでは、
Referrer-Policy: strict-origin-when-cross-origin
を推奨例として挙げている。
現在のブラウザにも比較的厳しい既定値があるため、昔のように「設定しないとURL全文が必ず外部へ送信される」という単純な話ではない。
それでもサイト側で方針を明示できる。
ただしno-referrerのように強く制限すれば、外部サービス側から見える参照元情報も減る。
アクセス解析や外部サイトとの連携でRefererを使っている場合は影響を確認する。
Permissions-Policyはcamera、microphone、geolocationなどのブラウザ機能を制限できる。
ブログで使わない機能を明示的に止める用途には使える。
一方、MDNではPermissions Policyを一部ブラウザで未対応の機能を含むものとして案内している。
将来、地図、動画、決済、iframe内の機能などを追加したときに、自分で設定したPolicyが原因で使えないこともある。
セキュリティヘッダーは、サイトの将来機能を知ったうえで値を決める必要がある。
大規模事故を見ると「1つのヘッダーで防ぐ」は危ない#
過去の大規模事故を見ると、ブラウザ側の防御を追加する意味は分かる。
同時に、セキュリティヘッダーだけで事故を防げるとは書けない。
British Airways#
2018年のBritish Airwaysの事故では、攻撃者がWebサイトのコードへ手を入れ、決済カード情報のコピーを攻撃者管理のドメインへ送信する状態が15日間続いた。
ICOの最終資料では429,612人が影響を受けたとしている。
このようなクライアント側のデータ送信に対し、厳格なCSPで接続先やform送信先を限定していれば、攻撃経路によっては被害を減らす追加防御になり得る。
ただし、ICOは複数のセキュリティ上の不足を問題としている。
「CSPがなかったから起きた事故」と単純化することはできない。
Ticketmaster#
Ticketmasterの2018年の侵害では、決済ページで利用していた第三者チャットボットのJavaScriptが問題になった。
ICO資料では、EEAで940万人が潜在的な影響対象として通知され、英国は150万人。カード情報などが対象になった。
ICOは、決済ページへ第三者scriptを入れる明確なリスクがある中で、Ticketmasterがlayered approachを十分に実装していなかったと指摘している。
CSPは第三者scriptの読み込み元や通信先を制限できる。
ただし、攻撃されたscriptの配信元をCSPですでに許可している場合、そのscript自体は動く。
通信先まで許可済みなら、CSPだけでは止められないこともある。
第三者scriptを減らす、内容を監視する、可能ならSubresource Integrityを使う、決済ページのような重要画面では依存関係を厳しくする。
ヘッダーはその一部になる。
この2件から見えるのは、「CSPを付ければ事故が起きない」ではなく、ブラウザにも制限を持たせるdefense in depthの考え方になる。
ゆまラボへ入れるならCSPを最後にする方が確認しやすい#
ゆまラボの現在の構成に合わせるなら、一度に強いCSPまで入れるより段階を分けたい。
比較的影響を確認しやすいのは、
X-Content-Type-Options
Referrer-Policy
frame制御
Permissions-Policy
になる。
HSTSは独自ドメインとサブドメインのHTTPS運用を確認してから。
CSPはGA4などの外部リソースを洗い出して、report-onlyから確認する。
実装時には、
トップページ
記事ページ
タグ・カテゴリページ
404ページ
GA4リアルタイム計測
XのOGP取得
画像・font
外部リンク
を確認したい。
特にGA4は、ページが普通に表示できても計測だけ止まる可能性がある。
アクセス解析カテゴリの記事として扱うなら、ここは外せない確認項目になる。
また、現在のゆまラボは静的配信なのでpublic/_headersが候補になるが、将来SSRやWorker処理へ移行した場合は同じファイルだけでは足りない。
セキュリティヘッダーはAstroのFrontmatterやコンポーネント設定ではなく、最終HTTP Responseの設定として管理する。
この境界を把握しておけば、サイト構成を変更した後も設定場所を追いやすい。
参考#
- Cloudflare Workers Docs - Headers for Static Assets
- Astro Docs - Cloudflare adapter / Headers
- OWASP - HTTP Security Response Headers Cheat Sheet
- OWASP - Content Security Policy Cheat Sheet
- Cloudflare - HTTP Strict Transport Security
- ICO - British Airways Penalty Notice
- ICO - Ticketmaster UK Limited Penalty Notice


