Notes / Web技術

CSP違反レポートのendpointを自前で持つ。Cloudflare Workerで最小構成を組む

CSPをサイトに入れる時、違反レポートをどこで受けるか。report-uri.comのようなSaaSを使うか、自前でendpointを立てるか、それともendpointなしで運用するか。3つの選択肢を比較して、Cloudflare Workerで30行程度の自前endpointを立てる手順とCORSの挙動。

CSP Cloudflare Workers security reporting API

CSPをサイトに入れようとすると、必ずぶつかる問いがあります。違反レポートを送る先をどうするか。

3つの選択肢がありました。SaaSの無料プランを使う、自前でendpointを立てる、endpointを持たずブラウザのConsoleで目視する。最初はSaaSが楽そうに見えますが、Cloudflareアカウントを既に持っているなら、自前で立てるのが学習効率と将来の引き出しの両方で得です。

3つの選択肢

SaaSの無料プラン

report-uri.comの無料枠は月10,000件まで受け取れます。アカウントを作ってendpoint URLをもらってヘッダーに貼るだけ。所要時間5〜10分。

メリットはダッシュボードが既にあること。月次レポートをPDFで出してくれるSaaSなら、客への「やってますよ報告」が省力化できます。

デメリットは外部SaaSにレポート(URL、User-Agent、違反directive)が送られること。公開サイトのデータなのでプライバシー問題は薄いですが、金融や大企業の客で「データを国外SaaSに送れない」と言われると即アウト。

自前でCloudflare Workerを立てる

CFアカウントが既にあるなら、追加SaaS契約なしでendpointを作れます。所要時間1〜1.5時間。

メリットは生のCSP違反JSONを自分の目で見ること。CSPの仕様、CORS preflightの挙動、新旧2形式(application/csp-reportapplication/reports+json)のフォーマット差まで触れます。SaaSダッシュボードを眺めるだけでは絶対に身に付かない領域。

デメリットは保守責任。受託先のために構築して引き渡すと、orphan化のリスクが残ります。けれど自社サイトなら自分で保守できる。

endpointなしで運用

Content-Security-Policy-Report-Onlyをヘッダーに入れて、report-urireport-toディレクティブを省略します。違反はChromeやSafariのConsoleにRefused to load ... because it violates ...として出るので、開発者が各ページを巡回して目視。

6〜20ページ規模のサイトなら現実的。コーポレートサイトで継続監視の必要が薄い時はこれで十分です。

どれを選ぶか

シナリオ推奨
中小〜中堅、社内に技術運用チームなしSaaS
既にCloudflareを使ってる客 (金融・大企業含む)自前Worker
6〜20ページの静的サイト、継続監視不要endpointなし
自社サイト、学習目的兼ねる自前Worker

このサイトでは「学習と将来のクライアント提案の引き出し」を理由に自前Workerを採用しました。一度組んでおけば、次回の提案で「自社サイトでこの構成で動かしてます」と言える。

最小構成のWorkerコード

worker/src/index.ts:

interface Env {}

const ALLOWED_ORIGINS = new Set<string>([
  'https://example.com',
  'https://example-staging.pages.dev',
]);

function isAllowedOrigin(origin: string | null): boolean {
  if (!origin) return false;
  return ALLOWED_ORIGINS.has(origin);
}

export default {
  async fetch(request: Request): Promise<Response> {
    const origin = request.headers.get('Origin');
    const corsOrigin = isAllowedOrigin(origin) ? origin! : 'null';

    if (request.method === 'OPTIONS') {
      return new Response(null, {
        status: 204,
        headers: {
          'Access-Control-Allow-Origin': corsOrigin,
          'Access-Control-Allow-Methods': 'POST, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type',
          'Access-Control-Max-Age': '86400',
          'Vary': 'Origin',
        },
      });
    }

    if (request.method !== 'POST') {
      return new Response('Method Not Allowed', { status: 405 });
    }

    let body: unknown;
    try {
      body = await request.json();
    } catch (e) {
      return new Response('Invalid JSON', { status: 400 });
    }

    console.log(JSON.stringify({
      kind: 'csp-report',
      timestamp: new Date().toISOString(),
      contentType: request.headers.get('Content-Type'),
      origin,
      userAgent: request.headers.get('User-Agent'),
      report: body,
    }));

    return new Response(null, { status: 204 });
  },
} satisfies ExportedHandler<Env>;

50行未満で完成です。wrangler.tomlは3行:

name = "example-csp-report"
main = "src/index.ts"
compatibility_date = "2026-05-30"

npx wrangler deployでデプロイ完了。デフォルトのworkers.devサブドメイン(例: example-csp-report.your-subdomain.workers.dev)が割り当てられます。

ストレージは最初なし、確認はwrangler tail

KVやD1やR2は使いません。console.logした内容をnpx wrangler tailでストリーミング表示します。

$ npx wrangler tail --format pretty

ターミナルに違反が届くたびに整形されたログが流れる。開発期間中はこれで十分。後から「過去の違反を一覧で見たい」となったらKVに移行できます。最初から作り込まないのが小さく始めるコツ。

CORS preflightへの対応

application/csp-report(旧形式)はSimple Requestなのでpreflightなし。application/reports+json(Reporting API、新形式)はpreflightを要求します。OPTIONSハンドラがないと違反が届きません。

Access-Control-Allow-Originはワイルドカード(*)ではなくorigin allowlistで具体的に返します。クライアント側がそのoriginをCSPで指定するため、ワイルドカードで受けても困らないですが、CSP report endpointとしては送信元を絞っておくのが筋。

ヘッダー設定

サイト側の_headers(Cloudflare Pages)に:

Reporting-Endpoints: csp-endpoint="https://example-csp-report.your-subdomain.workers.dev/"
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri https://example-csp-report.your-subdomain.workers.dev/; report-to csp-endpoint

Reporting-Endpoints(HTTPヘッダー)で送信先を定義し、CSPのreport-to csp-endpointで参照。後方互換のためreport-uriも同じURLで併記します。

副次的に得られるもの

Worker endpointを動かしておくと、Permissions-Policy違反、Network Error Logging、Document-Policy違反なども同じReporting APIで受けられます。CSPだけのために作ったendpointが、将来の他の監視にも使い回せる。

Reporting-Endpointsにendpointを増やすだけで、Workerコードはそのままで複数種類のレポートを受信可能。


CSP report endpointの選択は「外注するか自前で持つか」の判断です。SaaSは楽だけどデータ越境とベンダーロックインがある。自前Workerは保守責任が残るけど、CSP仕様への理解とクライアント提案の引き出しが手に入る。Cloudflareアカウントがあるなら、最小30行のWorkerで足りる構成から始められます。

Webセキュリティ設計のご相談はお問い合わせから、業務領域の詳細はServicesへ。