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 表示至少有一個錯誤。加上 --fail-on warning 則連警告也會導致失敗。加上 --explain 可輸出文字格式的說明與修復建議。JSON 輸出格式:{ summary: { error, warning }, findings: [{ rule, severity, message, file, line }] }。
如果結果顯示 0 SQL files,表示政策可能只存在於 Dashboard 中。請要求使用者執行 supabase db pull(它會將遠端結構寫入遷移檔案)後再掃描一次。不要在空掃描的結果下回報專案為乾淨。
檢查項目
| 規則 | 層級 | 意義 |
|---|---|---|
rls-disabled | 錯誤 | public 資料表從未啟用「enable row level security」,因此任何擁有 anon 金鑰的人都可以透過 API 讀取和寫入它。 |
rls-permissive-policy | 寫入為錯誤,讀取為警告 | using (true) 或 with check (true)。在 insert/update/delete 時,任何使用者都可以更改其他使用者的資料列。在 select 時,所有資料列都會公開。 |
rls-no-policy | 警告 | RLS 已啟用,但無任何政策:API 會拒絕所有操作。這對僅供伺服器存取的資料表來說沒問題;但如果瀏覽器讀取它,則會得到空列表。 |
rls-update-privileged-column | 警告 | 使用者可以更新自己的資料列,且該資料表包含 plan、role、is_admin、credits 等欄位。RLS 限制的是資料列,而不是欄位。 |
view-bypasses-rls | 錯誤 | 檢視未設定 security_invoker = true,它會以擁有者的身分執行,並跳過其底層資料表的 RLS。 |
auth-users-exposed | 錯誤 | public 中的檢視選取了 auth.users 的資料。 |
definer-search-path | 警告 | security definer 函數未設定 set search_path。 |
definer-function-anon | 錯誤 / 警告 | public 中的 security definer 函數,且 anon 角色可以透過 /rest/v1/rpc/<name> 呼叫。如果明確授予 anon 權限則為錯誤,如果是 Supabase 的預設權限則為警告。 |
storage-public-bucket | 錯誤 / 警告 | 公開的儲存桶。如果 storage.objects 上的 select 政策也允許任何人列出內容則為錯誤。 |
修復方式
請務必撰寫新的遷移檔案。切勿編輯已在資料庫上執行過的遷移檔案。
遺失或過於寬鬆的政策:詢問應用程式需要的存取權限,然後將每個政策限定到特定角色和資料擁有者。
alter table public.notes enable row level security;
create policy "notes: 讀取自己的" on public.notes
for select to authenticated using ((select auth.uid()) = user_id);
create policy "notes: 新增自己的" 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);(需要 PostgreSQL 15+,目前所有 Supabase 專案都支援)。具體化檢視(materialized view)無法使用 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檔案中的內容。Dashboard 中的變更、其他結構以及函數內部的邏輯都不在檢查範圍內。 - 乾淨的結果僅表示未找到這 9 種模式,並不代表政策完全符合應用程式的規則。仍然建議以真實的 anon 和已登入使用者身分進行測試(使用附有 anon 金鑰的 Supabase 用戶端)。
- 若要檢查瀏覽器和環境端(用戶端程式碼中的 service_role 金鑰、
NEXT_PUBLIC_變數中的機密、已提交的.env檔案),請執行不含--only的完整掃描,或使用vibe-preflight技能。