0018. Supabase への同一オリジン リバースプロキシ (企業DLPのドメイン遮断回避)¶
- ステータス: Accepted(実装済み・v1.30.1 で本番投入)
- 関連: 0002 バックエンドに Supabase を採用、0009 API を Cloudflare Workers (Hono) で構築、0010 環境分割 (dev / prod)
コンテキストと課題¶
Web (apps/web) は Cloudflare Pages 配信で、ブラウザから Supabase (auth = GoTrue / REST = PostgREST / Storage) を直接呼ぶ構成でした。本番の接続先はカスタムドメイン sb.waste-link.app(yybolwcdojbjwyymzskk.supabase.co への CNAME)。
2026-07 に一部テナント(西宮)でログイン不能が発生。原因は企業ネットワークの DLP / プロキシが Supabase ドメインを遮断・傍受していたこと:
sb.waste-link.app/auth/v1/tokenへの preflight 応答から CORS ヘッダが剥がされ、ブラウザがNo 'Access-Control-Allow-Origin' headerで失敗(Failed to fetch)。- 生ドメイン
*.supabase.co/auth/v1/healthは会社のブロック画面が返る。
つまり「ブラウザが Supabase ドメインへ直接 HTTP を出す」という前提そのものが、DLP 環境では成立しない。キー形式や CORS 設定の調整では解けない(ネットワーク層のドメイン遮断)。
決定¶
アプリと同一オリジンの Cloudflare Pages Function /supabase/* を経由して Supabase を中継する。 ブラウザからは「既に許可済みのアプリドメインへのリクエスト」にしか見えず、DLP のドメイン遮断を回避する。同一オリジンなので CORS preflight 自体が発生しない。Supabase への実ホップはエッジでサーバ側実行され、企業プロキシから不可視。
構成¶
functions/supabase/[[path]].ts(リポジトリ直下・CF Pagesroot_dir=""準拠):/supabaseプレフィックスを除いた残りをSUPABASE_PROXY_TARGET(Pages 環境変数・既定=本番 Supabase 生ドメイン)へ透過中継。メソッド・ヘッダ(apikey/Authorization含む)・ボディ・ステータスをそのまま転送。redirect: 'manual'、body はストリーム透過(content-encoding 二重化なし)。apps/web/src/lib/supabase.ts:VITE_SUPABASE_URL === '/supabase'(または'same-origin')のとき実行時オリジン配下${window.location.origin}/supabaseに解決。それ以外は絶対 URL を直用(既存挙動不変・ローカル開発は従来どおり)。有効化は本番 Pages env をVITE_SUPABASE_URL=/supabaseにして再ビルドするだけ。
セキュリティ上の論拠¶
- 転送先ホストは env で固定(
SUPABASE_PROXY_TARGET)。攻撃者が制御できるのは path/query/body のみで宛先ホストは固定なので、任意ホストへの SSRF にはならない(Supabase のパスにしか到達しない)。 - プロキシは Supabase 自身の認可(
apikey+ RLS + JWT)を一切迂回しない。公開されるのは「Supabase は元々インターネット公開」という既存事実と等価で、新たな権限は増えない。 - Realtime (WebSocket) は本アプリ未使用のため対象外。
結果¶
- 全 Supabase トラフィック(auth/REST/Storage)が同一オリジン → エッジ Function → 生 Supabase の経路になる。ブラウザは
sb.waste-link.appも生*.supabase.coも一切叩かない(sb.waste-link.appは設定は残るが休眠)。 - DLP のドメイン遮断・CORS 剥奪の影響を受けない。エッジ1ホップ増えるがレイテンシ影響は軽微(両者 CF エッジ)。
- ロールバックは本番 Pages env
VITE_SUPABASE_URLを絶対 URL に戻して再ビルドするだけ(可逆)。 - 本番全体のルーティング変更のため、preview での実ログイン経路(
POST /auth/v1/token)検証を投入前ゲートとする。