This page is a beginner-friendly, step-by-step guide to collecting CSP (Report-Only) violation reports safely: evolving from a simple “received” marker to daily JSONL logs, preventing log bloat, and preparing a clean path to SQLite-based aggregation.
CSP(Content Security Policy)を導入するとき、いきなりブロックを始めるのは不安ですよね。
そこでよく使われるのが Report-Only(監視モード)です。これは「ブロックはせずに、違反が起きたらブラウザがレポートを送る」仕組みです。
最初は「届いているか」を確実にするのが大事です。ここで詰まると全部進みません。
今回の到達点です。“どのページで / 何が / どのURLが”を追えるようになり、CSPの調整が現実的になります。
JSONLにしても、放置すればファイルは増えます。運用としては logrotate などで管理します。
やさしい説明:「危ない動きがあったら、ブラウザが“報告”してくれるモード」です。ブロックはしないので、まずは安全に様子見できます。
Content-Security-Policy-Report-Only ヘッダを返すreport-uri(またはReporting API)へJSONをPOSTするこのページでは、受信口(エンドポイント)側の作りと運用に集中します。
received だけのログは「届いている」確認としては優秀です。
でも、CSPの管理を進めるには次が分からないと調整できません。
document-uri)effective-directive / violated-directive)blocked-uri)そこで「1段階だけ進める」最適解が 日次JSONL です。
JSONL は「1行が1つのJSON」です。ログとして扱いやすく、あとで集計(grep/jq/SQLite)しやすいのが長所です。
ts:受信時刻(UTC推奨)ip:送信元IP(参考用。プロキシ経由なら扱い注意)ua:User-Agent(ノイズも多いので短めに)document_uri / blocked_urieffective_directive / violated_directivesource_file, line, col, status(ある場合のみ)保存先は、例えば次のように「日付で分ける」と運用がラクです。
/var/www/your-site/logs/csp-report-YYYYMMDD.jsonl
攻撃面を狭めます。GETで動く必要はありません。
CSPレポート送信はブラウザ実装に差があり、失敗時に再送が起きることもあります。ここで4xx/5xxを乱発すると、ログと負荷が増えやすいです。
Nginxの client_max_body_size と、PHP側の strlen チェックで守ります。
改行混入を無害化し、追記は LOCK_EX で競合を避けます。
ブラウザを待たずに、まずは curl で「確実に再現」するのが安全です。
curl -s -o /dev/null -w "%{http_code}
" \
-X POST https://example.com/csp-report \
-H 'Content-Type: application/csp-report' \
--data '{"csp-report":{"document-uri":"https://example.invalid/","blocked-uri":"https://evil.invalid/x.js","effective-directive":"script-src","violated-directive":"script-src","source-file":"https://example.invalid/app.js","line-number":12,"column-number":34,"status-code":200}}'
204
sudo ls -la /var/www/your-site/logs/ | grep csp-report-
sudo tail -n 3 /var/www/your-site/logs/csp-report-$(date -u +%Y%m%d).jsonl
"document_uri" や "blocked_uri" が見えるJSONLは「役に立つログ」ですが、放置するとファイルは増えます。
そこで運用として ログローテーション を入れます。
SELinux環境では、ローテ後に新規作成されたログのラベルがズレて書き込めなくなることがあるため、必要なら restorecon を組み合わせます(事故予防)。
SQLite化は「保存形式を変える」だけではなく、重複排除と日次集計で運用コストを下げる段階です。
次の段階では、例えば document_uri + effective_directive + blocked_uri をキーにして count++ することで、保存量を劇的に減らせます。
A. 受信エンドポイントは「落ちないこと」が最優先になりやすいです。壊れたJSONや想定外のContent-Typeで4xx/5xxを返すと、再送やログ増加で負荷が上がることがあります。必要な情報はサーバ側ログ(JSONLやエラーログ)で追う設計にします。
A. プロキシ/CDN経由ではヘッダで来るIPは偽装されやすいので注意が必要です。信頼できる経路(例:CDNのヘッダ)を優先し、X-Forwarded-Forは先頭のみ+妥当性検証をする、などが現実解です。
A. まずは 14日〜30日が現実的です。長期傾向が欲しくなったら、SQLiteの集計テーブルだけを長期保存する方が運用が軽くなります。
A. 入口で application/reports+json を許可し、ペイロード形式差を吸収する処理を追加していくのが安全です。まずは現行の “csp-report” 形式を確実に集めるのがおすすめです。