GYOSUI SkillMarket

supabase-security-check

検証済みv1.0.0

Supabase を使う Web ツールのデプロイ前に RLS・キー露出・アクセスパターンを点検し GO/CONDITIONAL/NO-GO を出す。「Supabaseのセキュリティチェック」「Supabase点検」「RLS確認」「デプロイ前Supabase監査」「supabase-security-check」等のトリガーで使用。汎用の /security-audit より軽量・Supabase専用。

ZIPダウンロード

Supabase セキュリティチェック

Supabase をバックエンドに使う Web ツールを本番/社内展開する前に、Supabase 側の設定起因の漏洩リスクを点検する。判定は GO / CONDITIONAL GO / NO-GO。断定より観測を優先し、各指摘に根拠(advisor lint 名・テーブル名)を添える。

前提条件

以下のいずれかで対象 Supabase プロジェクトにアクセスできること:

  • Supabase MCP(推奨): Claude Code / claude.ai に Supabase 連携(MCP)が、対象プロジェクトを持つアカウントで接続済みであること(list_organizations で見えるか確認。別アカウントだと権限エラー)。本スキルの手順は MCP のツール名(list_projects / get_advisors / list_tables 等)を前提に書かれている。
  • Supabase CLI: MCP が使えない環境では supabase login 済みの Supabase CLI(supabase projects list / supabase inspect db 等)で同等の情報を代替取得できる。手順中の MCP ツール呼び出しは CLI コマンドに読み替える。

対象プロジェクトの project_id を把握しておくこと(list_projects またはCLIで特定、もしくは利用者に確認)。

手順

1. プロジェクト特定

list_projects → 対象の project_id を確定(複数あれば利用者に確認)。

2. セキュリティ advisor

get_advisors(project_id, type="security") を実行し、lint を分類(下記「lint 解釈」)。remediation URL は利用者向けにそのまま提示する。

3. テーブル / RLS 状態

list_tables(project_id, schemas=["public"], verbose=false) で public スキーマの全テーブルを列挙し、RLS の有効/無効を確認。

4. アクセスパターン判定(最重要)

このツールが Supabase をどう読むかで、RLS の要否が真逆になる:

  • 特権接続方式: サーバー(API)が DATABASE_URL(postgres role)や service_role キーで読み書きする → RLS をバイパスする。この場合 RLS 有効・ポリシー無し = 正しい施錠(公開 API から全拒否)。
  • anon キー方式: フロント/クライアントが anon / sb_publishable キー + Supabase JS client で直接叩く → RLS ポリシーが必須。ポリシーが無いと「全拒否」でアプリが壊れる or 甘いポリシーだと漏洩。

見分け方: クライアント成果物に anon/publishable キー + createClient( があれば anon 方式。サーバーが接続文字列/service_role で叩くなら特権方式。

5. クライアント露出スキャン

ビルド成果物(dist/・バンドルJS)と設定ファイルを grep:

  • service_role / sb_secret … クライアントに出ていたら即 NO-GO(全 RLS をバイパスする最強キー)+ローテーション必須
  • postgresql:// / 接続文字列 … クライアント/リポジトリにあってはならない
  • anon / sb_publishable / eyJ(JWT) … anon 方式なら想定内。特権方式のツールに紛れていたら不要露出
  • 接続文字列(DATABASE_URL)はソース/git に無く、ホスティングの env(暗号化)のみにあること

6. (任意)パフォーマンス

get_advisors(project_id, type="performance") で未使用インデックス等も見る(セキュリティ判定には含めない)。

lint 解釈(Single Source of Truth)

lintlevel意味対応
rls_disabled_in_publicERRORpublic テーブルで RLS 無効 → anon キー + プロジェクトURL で誰でも読める原則 NO-GO。ALTER TABLE <t> ENABLE ROW LEVEL SECURITY;。anon 方式なら適切なポリシーも追加
rls_enabled_no_policyINFORLS 有効・ポリシー無しアクセスパターン次第。特権接続方式 → OK(意図した全拒否) / anon 方式 → ポリシー必須
security_definer_viewWARNSECURITY DEFINER ビュー定義者権限で実行。意図確認、必要なら security_invoker
function_search_path_mutableWARN関数の search_path 未固定SET search_path = '' 等で固定
auth_*(leaked password 等)WARNSupabase Auth 利用時のみAuth 未使用なら N/A

混同注意: 「RLS 有効・ポリシー無し(INFO)」は特権接続方式では"正しい"状態。ERROR の"RLS 無効"とは真逆。INFO を見て慌てて甘いポリシーを足すと逆に穴になる。

判定基準

IF service_role/接続文字列 がクライアントに露出:            → NO-GO
ELIF rls_disabled_in_public が1件以上 (public にデータ有):  → NO-GO(RLS有効化まで)
ELIF anonキー方式 かつ ポリシー未整備/過度に緩い:            → NO-GO / CONDITIONAL
ELIF WARN級(search_path 等)が残る:                          → CONDITIONAL GO
ELSE(特権方式 + 全テーブルRLS有効 + キー露出なし):         → GO

GO は「実施した Supabase 点検の範囲内でブロッカー無し」の意味。アプリ層の認証・ネットワーク・法令等は範囲外。

出力フォーマット

  • 判定(GO/CONDITIONAL/NO-GO)を先頭に
  • テーブル×RLS の一覧、advisor lint の分類(ERROR/WARN/INFO)
  • アクセスパターン(特権 or anon)と、それに照らした RLS 妥当性
  • クライアント露出スキャン結果
  • 各指摘に remediation リンク

教訓(実案件で見つかったパターン)

  • 特権接続方式のツールは「全テーブル RLS 有効・ポリシー無し」が正解(公開 API を全遮断し、アプリは接続文字列で動く)。
  • 逆に RLS 無効は ERROR:anon キー + URL だけで公開 API からテーブルが読める。実案件で、同様の構成の別ツールがこの状態だったことがある(RLS 有効化を推奨)。
  • 新規プロジェクトでも「RLS 有効化 + ポリシー無し + アプリは DATABASE_URL(postgres) 接続」を既定にすると堅い。