Supabase RLS監査
このスキルは、vibe-preflightスキャナーのデータベース部分を実行します(scripts/preflight.mjs、Node 20+、依存関係なし、ネットワーク不要)。プロジェクト内の .sql ファイル(Supabaseマイグレーション、schema.sql)を読み取り、テーブル、ポリシー、ビュー、関数、権限、ストレージバケットを順序どおりに再現し、9つの問題を報告します。データベースには接続しません。
実行方法
プロジェクトルートから:
node ${CLAUDE_SKILL_DIR}/scripts/preflight.mjs . --only database --format json
Claude Code は ${CLAUDE_SKILL_DIR} を自動補完します。他のエージェント:このスキルのフォルダ(例:.agents/skills/supabase-rls-audit または .claude/skills/supabase-rls-audit)を指定してください。
終了コード 1 は少なくとも1つのエラーがあることを意味します。--fail-on warning を追加すると警告でも失敗します。--explain(テキスト出力)で修正理由と方法を表示します。JSON出力:{ summary: { error, warning }, findings: [{ rule, severity, message, file, line }] }
結果が 0 SQL files の場合、ポリシーがダッシュボードにしか存在しない可能性があります。supabase db pull(リモートスキーマをマイグレーションファイルに書き出す)を実行してから再スキャンするようユーザーに依頼してください。空のスキャン結果をプロジェクトがクリーンだと報告しないでください。
チェック一覧
| ルール | レベル | 意味 |
|---|---|---|
rls-disabled | error | public テーブルで enable row level security が実行されていないため、anonキーを持つ誰でもAPIを通じて読み書き可能。 |
rls-permissive-policy | 書き込みはerror、読み取りはwarning | using (true) または with check (true)。挿入/更新/削除で任意のユーザーが他のユーザーの行を変更可能。選択ですべての行が公開される。 |
rls-no-policy | warning | RLSは有効だがポリシーなし。APIはすべての操作を拒否。サーバー専用テーブルなら問題なし。ブラウザで読み取ると空のリストが返る。 |
rls-update-privileged-column | warning | ユーザーが自身の行を更新でき、その行に plan、role、is_admin、credits などがある。RLSは行を制限するが、列は制限しない。 |
view-bypasses-rls | error | security_invoker = true がないビューは所有者として実行され、その基になるテーブルのRLSをバイパスする。 |
auth-users-exposed | error | public スキーマ内のビューが auth.users から選択している。 |
definer-search-path | warning | set search_path なしの security definer 関数。 |
definer-function-anon | error / warning | public スキーマ内の security definer 関数で、anonが /rest/v1/rpc/<name> から呼び出せるもの。anonへの明示的な権限付与がある場合はerror、Supabaseのデフォルト権限の場合はwarning。 |
storage-public-bucket | error / warning | パブリックバケット。storage.objects の選択ポリシーが誰でもリスト表示を許可している場合error。 |
修正方法
必ず新しいマイグレーションファイルを作成してください。データベース上で既に実行されたマイグレーションを編集しないでください。
ポリシーがない、またはパーミッシブな場合:アプリに必要なアクセス権限を確認し、各ポリシーをロールと所有者にスコープします。
alter table public.notes enable row level security;
create policy "notes: read own" on public.notes
for select to authenticated using ((select auth.uid()) = user_id);
create policy "notes: insert own" on public.notes
for insert to authenticated with check ((select auth.uid()) = user_id);
特権列:ユーザーが変更してもよい列のみ更新を許可します。
revoke update on public.profiles from authenticated;
grant update (display_name, avatar_url) on public.profiles to authenticated;
ビュー:alter view public.<name> set (security_invoker = true);(Postgres 15以上、現在のすべてのSupabaseプロジェクトで使用可能)。マテリアライズドビューはRLSを使用できません:public から移動するか、anon, authenticated からのselect権限を取り消してください。auth.users については、サインアップトリガーで共有可能なフィールドを public.profiles にコピーします。
SECURITY DEFINER関数:set search_path = '' を追加し、すべての名前をスキーマ付きで記述します。その後、呼び出し可能なロックをかける:
revoke execute on function public.admin_set_plan(uuid, text) from public, anon, authenticated;
grant execute on function public.admin_set_plan(uuid, text) to service_role;
Supabaseでは、public からの取り消しだけでは不十分です。anonは新しい関数に対して独自の権限を持っているためです。
ストレージ:プライベートバケットはプライベートにし、読み取りを所有者のフォルダ((storage.foldername(name))[1] = (select auth.uid())::text)にスコープし、署名付きURLを提供します。製品画像やロゴはパブリックのままで構いません。
意図的なもの(公開カタログ、サーバー専用テーブルなど)は、該当行またはその上の行に -- vibe-preflight-ignore <rule-id> を付けることで警告を抑制できます。同じコメントに理由を記載してください。
報告手順
- 検出された各行をSQLと照合して確認してから変更してください。スキャナーはSQLテキストを読み取るため、誤検出の可能性があります。誤りの場合はその旨を伝えてください。
- ルールごとに結果をグループ化します。エラーを最初に表示し、次にユーザーと確認した警告を表示します。
- 修正後にスキャンを再度実行し、新しい結果を報告します。
制限事項
.sqlファイル内のものだけが対象です。ダッシュボードのみの変更、他のスキーマ、関数本体の内部処理はチェックされません。- クリーンな結果は、これら9つのパターンが見つからなかったことを意味します。ポリシーがアプリのルールと一致していることを証明するものではありません。実際のanonユーザーおよび認証ユーザーとしてのテスト(anonキーを使用したSupabaseクライアント)は依然として推奨されます。
- ブラウザ側や環境変数(クライアントコード内のservice_roleキー、
NEXT_PUBLIC_変数内のシークレット、コミットされた.env)については、--onlyなしでフルスキャンを実行するか、vibe-preflightスキルを使用してください。