Webサイトのフロントエンド実装におけるセキュリティをまとめたノートになります。適用する範囲や強さは要件と運用体制によって変わるので注意が必要です。
確認
実装を始める前に次の項目を確認します。
- ログインや個人情報、決済、ユーザー入力を扱うか
- 適用するセキュリティ基準や発注者の要件があるか
- 対象となるページや環境、ドメインはどこまでか
- 外部スクリプトやフォント、動画、地図、APIを利用するか
- CSP違反のレポートを誰が確認するか
- 公開後に外部サービスやポリシーを更新する担当者は誰か
- 対象ブラウザと検証方法が決まっているか
HTTPレスポンスヘッダーはサーバーやCDNから返します。フロントエンドやバックエンド、インフラ、運用の担当範囲を先に整理します。特にCSPは外部サービスを追加するたびに見直しが必要です。制作時に設定できても公開後の更新担当が不明なままではポリシーと実装がずれていきます。
CSPで許可する範囲を決める
Content-Security-Policyはページが読み込みまたは実行できるリソースをブラウザへ伝える仕組みです。意図しないスクリプトの実行や外部への接続を制限します。XSSなどの影響を抑えます。
例えば次のポリシーでは基本的なリソースを同一オリジンに限定します。プラグインコンテンツ、base要素による基準URLの変更、他サイトからのフレーム埋め込みも禁止しています。
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
form-action 'self' これは各ディレクティブの関係を確認するための例です。必要な接続先はサイトごとに異なります。script-src 'self'だけではnonceまたはハッシュを使うStrict CSPと同じ強度にはなりません。そのまま本番へ設定するのではなく実際のリソースと要件に合わせて組み立てます。
主に確認するディレクティブは次の通りです。
| ディレクティブ | 主な対象 | 制作時に確認するもの |
|---|---|---|
default-src | 個別指定がないリソース | 他のディレクティブに対する既定値 |
script-src | JavaScript | バンドル、アクセス解析、タグ管理、インラインスクリプト |
style-src | CSS | CSSファイル、インラインスタイル、JavaScriptが追加するスタイル |
img-src | 画像 | CDN、CMS、SVG、data: URL、blob: URL |
font-src | Webフォント | フォントファイルの配信元 |
connect-src | fetch()、XHR、WebSocketなど | API、アクセス解析、エラーログ、ストリーミング接続 |
media-src | 音声と動画 | 動画CDN、音声ファイル、blob: URL |
worker-src | Worker | Web Worker、Service Worker、blob: URL |
frame-src | ページ内へ埋め込むフレーム | 動画、地図、外部フォーム |
frame-ancestors | このページを埋め込める親 | 外部サイトからのiframe表示 |
form-action | フォームの送信先 | 問い合わせ、検索、外部フォーム |
一覧にない接続先を違反ログへ出るたびに追加すると何のための許可か分からなくなります。まずページが利用するリソースをリストアップして用途と担当者を残します。
| 種類 | 接続先 | 用途 | 必須か | 失敗した場合 |
|---|---|---|---|---|
| Script | analytics.example | アクセス解析 | 任意 | 計測されない |
| Font | fonts.example | Webフォント | 任意 | システムフォントへ切り替える |
| Connect | api.example | コンテンツ取得 | 必須 | 対象コンテンツを表示できない |
ここでは実在しないドメインを使っています。実案件ではホスト名だけでは足りません。誰が利用を決め公開後に誰が変更するかまで確認します。
CSPと衝突しやすい実装
インラインスクリプト
script-srcを厳しくするとHTML内のscriptやonclickなどのインラインイベントハンドラーは実行されません。
<button type="button" onclick="openMenu()">メニュー</button> イベントはJavaScript側で登録します。HTMLへ実行するコードを置かない構成にします。
<button class="js-menu" type="button">メニュー</button> const button = document.querySelector( '.js-menu' );
button.addEventListener( 'click', openMenu ); 既存実装が動かないときに'unsafe-inline'を追加すると表示は戻るかもしれません。ただインラインコードを広く許可するためCSPを厳しめに設定する目的と衝突します。まずコードを外部ファイルへ移すかnonceまたはハッシュで許可する範囲を限定できないか確認します。
文字列からのコード実行
eval()やnew Function()のように文字列をJavaScriptとして評価する処理は通常の厳しめなCSPでは拒否されます。動かすために'unsafe-eval'を追加する前にその処理を除去または置換します。
開発用ビルドだけがコード生成を利用している場合もあるので本番ファイルを対象に確認し開発環境を動かすための例外を本番ポリシーへ残さないように注意が必要です。WebAssemblyを利用する場合は'wasm-unsafe-eval'を含め対象ブラウザと要件を分けて確認します。
インラインスタイル
style-srcを厳しくするとHTMLのstyle属性やHTML内のstyle要素、JavaScriptから追加されるスタイルが影響を受けます。ライブラリが実行時にstyle要素を生成している場合もあるので自分で書いたHTMLだけを検索しても十分ではありません。
アニメーションで要素のstyleを更新する実装と文字列から新しいCSS規則を挿入する実装も分けて確認します。適用されるCSPの条件が異なるため実際の対象ブラウザで違反と表示結果を確認します。
data:とblob:
画像の埋め込みや動画、Worker、生成したファイルなどでdata: URLやblob: URLを使うことがあります。必要なディレクティブへ個別に指定できます。ただ利便性だけを理由にdefault-srcへ広く追加しない方が管理しやすくなります。
WebGLを使うサイトではテクスチャや動画、Web Worker、WebAssemblyなど読み込み経路が増えます。Three.jsを許可するという単位ではなく最終的にブラウザがどの種類のリソースをどこから取得するかを確認します。
外部サービス
タグ管理やアクセス解析、動画、地図、チャットなどでは一つのスクリプトが別のスクリプトやフレームを追加することがあります。最初のURLだけをscript-srcへ追加しても動作するとは限りません。
動かない接続先をすべて許可しません。そのサービスが必須か自前配信へ変更できるか同じ目的の別の方法がないかを確認します。許可するドメインが多いほど管理対象も増えます。
nonceとハッシュ
Strict CSPではホスト名の許可リストだけに依存しません。nonceまたはハッシュを使って実行できるスクリプトを指定します。
nonceを使う場合は推測できない値をHTTPレスポンスごとに生成します。CSPと許可するscript要素の両方へ同じ値を設定します。
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none' <script nonce="{RANDOM}" src="/assets/main.js"></script> {RANDOM}は固定文字列ではありません。サーバーがレスポンスごとに生成する値です。同じnonceを継続して使うと仕組みの前提が崩れます。
静的HTMLをそのまま配信する場合は対象スクリプトの内容からハッシュを生成して許可する方法があります。ただしファイルを更新するたびにハッシュも更新する必要があります。nonceとハッシュのどちらが適しているかはHTMLの生成方法とデプロイ工程から決めます。
'strict-dynamic'を指定するとnonceまたはハッシュで信頼したスクリプトが追加するスクリプトにも信頼が引き継がれます。外部サービスを扱いやすくなる一方で信頼したスクリプト内のDOM操作も含めて確認する必要があります。
Report-Onlyから始める
既存サイトへCSPを一度に適用すると必要なリソースまで拒否してページが動かなくなる可能性があります。まずContent-Security-Policy-Report-Onlyで候補となるポリシーを配信します。
Reporting-Endpoints: csp-endpoint="https://reports.example/csp"
Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'none'; report-to csp-endpoint ここでは実在しないレポート送信先を使っています。Report-Onlyでは違反を送信しますがリソースの読み込みや実行は遮断しません。ブラウザの対応状況によってReporting APIの挙動が異なる可能性があるため対象ブラウザでレポートの受信まで確認します。
確認は次の順で進めます。
- 開発者ツールのNetworkで通信先をリストアップする
- 候補となるポリシーをReport-Onlyで設定する
- 主要なページと操作を対象ブラウザで確認する
- 違反の原因を必要な通信、修正すべき実装、不要な通信へ分ける
- コードまたはポリシーを修正する
- 強制する
Content-Security-Policyへ切り替える - 公開後も外部サービスの変更と違反を確認する
ブラウザ拡張機能などサイトの実装に由来しない違反が混ざることもあります。ログに出たドメインを確認せず許可リストへ加えないようにします。
違反レポートを外部へ送信する場合はレポートに含まれるURLや運用方法も確認します。受信する値は信頼しません。保存する項目やアクセス制限、レート制限、保存期間を決めます。セキュリティのためのログへ不要な情報を残さない設計が必要です。
DOMへ文字列を入れる処理
CSPを設定してもユーザー入力や外部データを安全に扱う必要があります。単なるテキストを表示する場合はHTML文字列を組み立てずtextContentを使えます。
const title = document.createElement( 'h2' );
title.textContent = data.title;
container.appendChild( title ); innerHTMLやinsertAdjacentHTML()、document.write()など文字列をHTMLやスクリプトとして解釈できる場所はInjection Sinkと呼ばれます。外部から得た値をそのまま渡さず本当にHTMLが必要かを最初に確認します。
HTMLを受け取る必要がある場合は許可する要素と属性を決めてサニタイズします。さらに厳しい要件ではTrusted TypesをCSPから強制し対象のAPIへ通常の文字列を渡せない状態にできます。
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types article-html Trusted Typesの導入は既存ライブラリやブラウザ対応にも影響します。CSPと同様に設定だけを追加せずInjection Sinkをリストアップしてから移行します。
外部ファイルとSRI
CDNなど別の管理主体からJavaScriptやCSSを読み込む場合はSubresource Integrityを使って取得したファイルのハッシュを検証できます。
<script
src="https://cdn.example/library.js"
integrity="sha384-{HASH}"
crossorigin="anonymous"
></script> ファイルの内容が変わるとハッシュが一致しません。ブラウザは読み込みを拒否します。改変への対策になる一方で外部ファイルが自動更新されるサービスとは相性が難しい場合があります。更新方法を固定できない場合は自前配信も含めて検討します。
別オリジンのリソースをSRIで検証する場合は配信元がCORSを許可していることも必要です。ハッシュだけを追加して完了とせず実際のレスポンスヘッダーと読み込み結果を確認します。
CSP以外のポリシー
CSP以外のHTTPレスポンスヘッダーにもそれぞれ異なる役割があります。数を増やすこと自体を目的にしません。サイトが利用する機能と影響を確認します。
Permissions Policy
Permissions-Policyはカメラやマイク、位置情報などブラウザ機能の利用範囲を制御します。使用しない機能を無効にする例は次の通りです。
Permissions-Policy: camera=(), microphone=(), geolocation=() 外部のiframeへ機能を許可する場合はレスポンスヘッダーとiframeのallow属性の両方を確認します。対象機能とブラウザの対応状況は更新されるため一覧を固定したまま使い続けないようにします。
Referrer Policy
Referrer-Policyはリンク遷移やリソース取得時に遷移元の情報をどこまで送るかを制御します。
Referrer-Policy: strict-origin-when-cross-origin 外部オリジンへはパスを送らずオリジンだけを送る設定です。より厳しくno-referrerにすることもできます。ただアクセス解析や外部サービスが参照元を必要とする場合があります。情報を減らす効果と運用への影響を合わせて判断します。
COOPとCOEP
Cross-Origin-Opener-PolicyとCross-Origin-Embedder-Policyは別オリジンのページやリソースから閲覧コンテキストを分離するために使います。両方を適切に設定するとCross-Origin Isolationが必要な機能を利用できます。
ただし外部の画像や動画、iframe、認証用のポップアップなどへ影響する可能性があります。一般的なサイトへ常に設定するものではありません。SharedArrayBufferなど分離を必要とする機能と読み込む外部リソースを確認してから導入します。
X-Content-Type-Options
X-Content-Type-Options: nosniffはレスポンスのContent-Typeと異なる形式をブラウザが推測して扱うことを抑制します。
X-Content-Type-Options: nosniff このヘッダーを設定するだけではありません。JavaScriptやCSS、フォント、画像を正しいContent-Typeで配信します。設定後に読み込めなくなったファイルがあればnosniffを外すのではなく配信側のMIME Typeを確認します。
HTTPSとHSTS
HTMLだけでなくJavaScriptや画像、フォント、APIを含む通信をHTTPSへ統一します。HTTPSのページからHTTPのリソースを読み込むMixed Contentはブラウザに拒否される場合があります。CSPの設定以前に通信先を修正する必要があります。
Strict-Transport-Securityは一度HTTPSで接続したブラウザに対してその後もHTTPSだけを利用するよう伝えます。
Strict-Transport-Security: max-age=31536000 長いmax-ageやincludeSubDomains、preloadは影響が長く残ります。証明書の更新やサブドメイン、障害時の対応を確認せず一律に追加しないようにします。HSTSはフロントエンドのJavaScriptから設定するものではありません。配信と運用を含めて判断します。
CORSは許可を広げるための設定ではない
外部APIやCDNからリソースを取得できないときAccess-Control-Allow-Origin: *を追加して解決しようとすることがあります。ただCORSは別オリジンのページからレスポンスを読み取れる範囲をサーバー側が伝える仕組みです。
エラーを消すためだけに対象オリジンを広げません。そのリソースを誰が読み取ってよいかを確認します。またCSPのconnect-srcで接続を許可しても接続先のCORS設定が許可していなければJavaScriptからレスポンスを利用できません。二つは別の制御です。
レスポンスヘッダーを確認する
CSPを含む多くのポリシーはフロントエンドのJavaScriptから設定するものではありません。サーバーやCDN、ホスティングサービスなどからHTTPレスポンスヘッダーとして返します。
HTMLのmeta要素からCSPの一部を指定することもできます。ただReport-Onlyやframe-ancestorsなど利用できない機能があります。要件が厳しい場合はHTTPレスポンスヘッダーを基本にします。
設定後はブラウザのNetworkだけでなくコマンドからも実際のレスポンスを確認できます。
curl -I https://www.example.com/ トップページだけでなく下層ページやエラーページ、別言語ページなどHTMLを返す経路も確認します。CDNのキャッシュや環境差によってステージングと本番で異なるヘッダーが返る可能性があります。
制作開始時のチェックリスト
セキュリティ要件がある案件では次の項目を制作工程へ入れます。
要件確認
- 適用する基準と対象範囲を確認する
- 発注者や開発、インフラ、運用の担当範囲を決める
- 対象ブラウザと検証環境を決める
- 公開後の更新担当と連絡経路を決める
設計と実装
- 外部ドメインとリソースの用途を一覧にする
- インラインスクリプトとインラインスタイルを確認する
eval()やnew Function()、Injection Sinkを確認するdata:URLやblob:URL、Worker、WebAssemblyの利用を確認する- 外部サービスが追加する通信と
iframeを確認する - nonceまたはハッシュを生成する工程を決める
検証と公開
- Report-Onlyで主要ページと操作を確認する
- 違反を許可、実装修正、不要な通信へ分類する
- 本番ビルドと本番相当の配信環境で再確認する
- 強制後にフォームや外部遷移、動画、解析などを確認する
- 下層ページとエラーページのレスポンスヘッダーを確認する
- 公開後の違反確認とポリシー更新を運用へ引き継ぐ
参考
- Content Security Policy Level 3 – W3C
- Content Security Policy – MDN
- CSP implementation – MDN
- Permissions Policy – W3C
- Referrer Policy – W3C
- Trusted Types – W3C
- Trusted Types API – MDN
- Subresource Integrity – MDN
- Cross-Origin-Opener-Policy – MDN
- Cross-Origin-Embedder-Policy – MDN
- Strict-Transport-Security – MDN
- Fetch Standard – WHATWG
- ※ 記載内容は2026年7月22日時点の公開仕様と公式ドキュメントを基準にしています。