Reportingとエラー収集の仕組み(report-to/CSPレポート)
本番のCSP違反やレンダラークラッシュを取りこぼさず自動回収する仕組み。Reporting API の配送モデルと report-to、window.onerror との役割分担を原理から押さえ、穴のない監視を組めます。
- 1.Reporting API はブラウザが生成する違反・非推奨・クラッシュ報告を、Reporting-Endpoints ヘッダで宣言した名前付きエンドポイントへ、ブラウザ自身がバッチ化して非同期 POST する仕組み。ページの JS は介在しない。
- 2.CSP・COOP・Document-Policy などのレポートは report-to ディレクティブでエンドポイント名を指すだけで配送される。旧 Report-To ヘッダ+report-uri は後方互換、現行は Reporting-Endpoints と report-to の組み合わせ。
- 3.window.onerror/unhandledrejection は同一ドキュメント内で同期的に拾う JS のフックで、ブラウザ起因の違反やレンダラークラッシュは捕捉できない。両者は補完関係で、片方だけでは監視に穴が残る。
本番環境で起きる Content Security Policy 違反や、廃止予定 API の利用、レンダラーのクラッシュは、ユーザーの画面の中で起きるため開発者には見えません。Reporting API は、こうしたブラウザ自身が検知したイベントを構造化レポートとしてサーバーへ自動送信する標準機構です。ページの JavaScript を介さずブラウザが直接配送する点が肝で、JS が止まるような状況でも報告が届きます。ここでは配送モデルと、window.onerror 系との役割分担を原理から見ていきます。
レポートはどこから生まれ、どう配送されるか
レポートを生み出すのはアプリケーションコードではなくブラウザです。CSP のソースリスト照合に失敗した、非推奨 API を呼んだ、Permissions-Policy で禁止した機能を使おうとした、レンダラープロセスが落ちた——こうした瞬間にブラウザがレポートオブジェクトを内部キューへ積みます。各レポートは type(csp-violation/deprecation/intervention/crash など)、url、そして型ごとの body を持つ JSON です。
配送先はレスポンスヘッダで宣言します。現行仕様は Reporting-Endpoints で、名前=URL の形でエンドポイントに名前を付けます。
Reporting-Endpoints: main="https://reports.example.com/main",
csp="https://reports.example.com/csp"
ブラウザはレポートを即時には送りません。同じエンドポイント宛のレポートを一定時間まとめてバッチ化し、Content-Type: application/reports+json の配列ボディとして非同期 POST します。送信はベストエフォートで、ページのライフサイクルとは独立して動くため、ユーザーが離脱した後でも届くことがあります。これが「JS が介在しない」配送の実体です。
旧世代は Report-To ヘッダで JSON のエンドポイントグループを定義し、CSP からは report-to グループ名 で参照しました。現行の Reporting API v1 はこれを Reporting-Endpoints ヘッダ(単純な 名前=URL 構文)へ置き換えています。さらに古い report-uri ディレクティブは URL を直接書く方式で、これだけは別フォーマット(application/csp-report、単一オブジェクト)で送られます。互換のため report-uri と report-to を両方書くのが実務の定石です。
CSP・COOP レポートの紐付け
セキュリティポリシー側は、エンドポイントの名前を指すだけでレポートを配送できます。CSP なら report-to ディレクティブにエンドポイント名を書きます。
Content-Security-Policy: script-src 'self'; report-to csp
Reporting-Endpoints: csp="https://reports.example.com/csp"
違反が起きると、ブラウザは csp-violation 型のレポートを生成し、csp という名前のエンドポイント宛キューへ積みます。body には blockedURL、violatedDirective、effectiveDirective、originalPolicy、disposition(enforce か report)などが入ります。Content-Security-Policy-Report-Only を使えばブロックせずレポートだけ集められ、本番強制前の検証に使えます。この段階的導入の考え方は Content Security Policyの内部動作と回避耐性 で詳述しています。
COOP(Cross-Origin-Opener-Policy)も同じ仕組みに乗ります。report-to パラメータを付けると、ポップアップやオープナーとの分離に関する違反が coop 型などで届きます。COOP は同一オリジンポリシーとサイト分離の信頼境界が定めるプロセス分離・クロスオリジン隔離を運用面で支えるもので、レポートは「分離が想定通りか」を本番で確認する手段になります。
| レポート型 | 発生源 | 主な body フィールド |
|---|---|---|
| csp-violation | CSP のソースリスト照合失敗 | blockedURL / violatedDirective / disposition |
| deprecation | 非推奨 API の呼び出し | id / message / anticipatedRemoval |
| intervention | ブラウザがユーザー保護で介入 | id / message |
| coep / coop | クロスオリジン隔離ポリシー違反 | disposition / type |
| crash | レンダラープロセスの異常終了 | reason(oom / unresponsive 等) |
Permissions-Policy 由来の Permissions-Policy-Report-Only も同経路で配送されます。どの機能がどのフレームで拒否されたかを把握でき、ポリシー設計の検証に有効です。これは Permissions Policyによる機能ゲート と組み合わせて使います。
ReportingObserver — ページ内から観測する
サーバー配送とは別に、ページの JS から同じレポートストリームを読む手段が ReportingObserver です。これはネットワーク送信ではなく、ブラウザが生成したレポートをドキュメント内で受け取るオブザーバです。
const observer = new ReportingObserver((reports, observer) => {
for (const report of reports) {
// report.type === "deprecation" など
console.log(report.type, report.body);
}
}, { buffered: true });
observer.observe();
buffered: true を指定すると、オブザーバ生成前に溜まっていたレポートも初回コールバックで受け取れます。CSP 違反のように設計上 JS へ露出しない型もありますが、deprecation や intervention はここで拾えるため、自前のテレメトリへ流す用途に向きます。サーバー配送が「ブラウザ→自分のサーバー」なのに対し、ReportingObserver は「ブラウザ→同一ドキュメントの JS」で、後者はレポートを加工・サンプリングしてから送りたいときに使います。
window.onerror/unhandledrejection との関係
ここを混同すると監視に穴が空きます。window.onerror と unhandledrejection は JavaScript 実行時に発生した例外を捕まえるフックで、Reporting API とは発生源も配送経路も異なります。
window.onerror(またはaddEventListener('error'))は、スクリプト実行中にスローされ捕捉されなかった例外を同期的に同一ドキュメント内で受け取ります。クロスオリジンのスクリプトでは、CORS のcrossorigin属性が無いとメッセージがScript error.に丸められ、スタックも失われます。unhandledrejectionは、.catchされなかった Promise の reject を非同期に拾います。awaitの失敗を握りつぶしたコードはここに現れます。
これらは「アプリのコードが投げたエラー」を拾うもので、配送は自分で fetch するなど JS の責任です。一方 Reporting API が扱うのは「ブラウザが検知したポリシー違反やプロセスの状態」で、CSP 違反やレンダラークラッシュは JS の例外ではないため window.onerror には一切現れません。逆に、自前の throw new Error() は Reporting API のレポートにはなりません。
JS の例外監視(onerror/unhandledrejection)と、ブラウザ起因のレポート(CSP・deprecation・crash)は捕捉できる事象が排他的です。例外監視だけだと CSP でブロックされた読み込みやレンダラーの OOM クラッシュを見逃し、Reporting だけだと自前ロジックのバグを見逃します。本番監視では両方を併用し、crash レポートで「そもそも JS が動かなかったセッション」まで可視化するのが堅い構成です。
信頼境界とエンドポイント設計
レポートエンドポイントには Origin ヘッダ付きの POST が届きますが、ブラウザによっては配送前にプリフライト相当の確認を行います。受信側は送信元 url を検証し、攻撃者が偽レポートを大量送信する DoS/ノイズ混入に備えてレート制限を入れるべきです。レポートは Service Worker の fetch ハンドラを経由しない点にも注意が必要で、Service Workerのライフサイクルとスコープ制御で扱う傍受はレポート配送には効きません。これは、SW 自体が壊れていてもレポートが届くようにするための設計です。
本番強制している CSP とは別に、より厳しいポリシーを -Report-Only で常設しておくと、将来の strict 化に向けた違反を本番トラフィックで継続収集できます。同様に deprecation レポートを集めておけば、依存ライブラリが踏んでいる廃止予定 API を、実際に削除される前に検知できます。Reporting API の価値は、こうした「まだ壊れていないが将来壊れる」兆候を、ユーザーの実環境から先回りで拾える点にあります。
Web/フロントエンド Article
Reportingとエラー収集の仕組み(report-to/CSPレポート)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
セキュリティ
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
CSP・COOP・Document-Policy などのレポートは report-to ディレクティブでエンドポイント名を指すだけで配送される。旧 Report-To ヘッダ+report-uri は後方互換、現行は Reporting-Endpoints と report-to の組み合わせ。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「セキュリティ / Web」に近いか確認する。
- 強みである「Reporting API はブラウザが生成する違反・非推奨・クラッシュ報告を、Reporting-Endpoints ヘッダで宣言した名前付きエンドポイントへ、ブラウザ自身がバッチ化して非同期 POST する仕組み。ページの JS は介在しない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。