// docs / baas security / supabase rls scanner
Supabase RLS 掃描器:找出缺少或損毀的資料列層級安全表
當你部署一個以 Supabase 為後端的應用程式時,資料列層級安全 (RLS) 是站在你客戶資料與網際網路之間的唯一屏障。AI 編碼工具會產生可編譯、上線並悄悄外洩資料的 RLS 形狀程式碼 — 建立時未啟用 RLS 的資料表、可讀卻從不限制的策略、將欄位與自身比較的述詞。本文展示 Supabase RLS 掃描器從外部可以證明什麼、在 vibe 編碼應用中出現的四種 RLS 損毀型態,以及如何在一分鐘內掃描你自己的部署。
外部 RLS 掃描可以證明什麼
A passive RLS scan looks at your Supabase project the way an anonymous visitor would. It uses only the publishable anon key — the same key your browser uses — and never authenticates as a user or touches service-role privileges. Anything it can see, an unauthenticated attacker on the internet can see.
從資料庫外部,掃描器可以高信心地確認下列項目:
- 資料表上 RLS 已停用。當 RLS 關閉或策略允許時,PostgREST 會為匿名
SELECT傳回資料列。任一情況都是一項發現。 - 匿名角色可以列出資料表。使用 anon 金鑰的
GET /rest/v1/會傳回anon角色擁有任何權限的每張資料表的 OpenAPI 結構。AI 生成的應用程式經常授予結構描述的USAGE與每張資料表的SELECT,即使 RLS 拒絕實際讀取,也會暴露完整的結構描述地圖。 - Read access does not establish write safety. A passive scan cannot prove that INSERT, UPDATE, or DELETE policies protect every user and tenant. Review write policies and test them with isolated fixtures under the intended roles.
- 服務角色金鑰在瀏覽器套件中。與 RLS 相鄰:如果掃描器在 JavaScript 套件中找到
SUPABASE_SERVICE_ROLE_KEY或任何帶有role: service_role的 JWT,RLS 就形同虛設 — 該金鑰持有者可以繞過所有策略。
外部掃描無法證明什麼
請誠實面對掃描器的邊界。外部 RLS 掃描無法讀取你的 pg_policies 資料表、移轉檔案或任何策略的確切述詞。它從黑盒行為推論,這代表它有時會回報最終被證實為刻意公開資料 (行銷電子報資料表、公開產品目錄) 的 發現。當掃描器無法消除意圖歧義時,FixVibe 報告會將其標示為中等信心 — 請審視資料表名稱並自行判斷。
AI 工具產生的四種 RLS 損毀型態
When you point Cursor, Claude Code, Lovable, or Bolt at Supabase, the same four broken-RLS patterns come up again and again. Each one passes type-check, compiles, and ships:
型態 1:從未啟用 RLS
The most common failure mode. The migration creates the table but the developer (or the AI tool) forgets ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST happily serves the entire table to anyone with the anon key. Fix: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;, then add a policy per command. Enabling RLS with no policies denies all access through the API; the policies decide who gets in.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;型態 2:已啟用 RLS,但無策略
一種更隱晦的失敗。RLS 已啟用,但沒有撰寫策略。PostgreSQL 的預設是拒絕,因此已驗證使用者什麼也看不到 — 於是開發者加入 USING (true) 讓應用程式能運作,這允許所有人讀取所有內容。修復:撰寫以 auth.uid() 限定範圍的策略:CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); 以及對應的 INSERT/UPDATE/DELETE 策略。
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);型態 3:策略將欄位與自身比較
一個複製貼上的產物。開發者寫 USING (user_id = user_id) — 永遠為真 — 而非 USING (auth.uid() = user_id)。型別檢查通過;策略允許每一列。修復:永遠將欄位與函式呼叫比較 (auth.uid()、auth.jwt()->>'org_id' 等),絕不與自身或常數比較。
Shape 4: Overly permissive write policies
With RLS enabled, a role with no applicable policy is denied by default. A SELECT policy does not itself allow INSERT. Write exposure can arise from a permissive applicable INSERT or ALL policy, disabled RLS, or a role that bypasses RLS. Fix: inspect table privileges and every applicable policy, use ownership-scoped WITH CHECK conditions, and test writes with separate user identities.
FixVibe Supabase RLS 掃描器的運作方式
FixVibe's Supabase check works from the deployed app, in three steps:
- Find the project. FixVibe loads the deployed app and reads the Supabase project URL and public key from the same configuration your browser receives. No guessing, no brute force.
- See what is exposed. FixVibe checks which tables the anonymous role can see, without reading row data at this step.
- Stage 3 — inspect anonymous read access. The passive check reports observable access to discovered resources. It does not create, update, or delete database rows, and it cannot establish that all write policies or cross-user boundaries are correct.
Each finding ships with the exact request URL, response status, response shape (header-only), and the table name. FixVibe now labels whether the fix belongs in code/config, a provider console, DNS, secret rotation, or manual review so the next step matches who can actually apply it.
掃描器發現問題時該怎麼做
每一項 RLS 發現都是執行時的緊急事件。公開的 PostgREST 端點會在幾分鐘內被攻擊者掃描。修復順序是機械化的:
- 稽核每張資料表。在 Supabase SQL 編輯器中執行
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';。任何rowsecurity = false的列都是問題。 - Enable RLS on every public table. Default to
ENABLE ROW LEVEL SECURITYon every table created — make it a migration template. - 逐一命令撰寫策略。不要使用
FOR ALL USING (true)。為 SELECT、INSERT、UPDATE、DELETE 各寫明確策略 — 每個都以auth.uid()或來自auth.jwt()的組織 ID 欄位限定範圍。 - 用第二個帳號驗證。註冊為不同使用者,嘗試透過 REST API 直接讀取另一名使用者的記錄。如果回應是
200,則策略已損毀。 - Re-scan. After applying the fix, re-run a FixVibe scan against the same URL. The Supabase RLS finding should clear.
-- Audit every table for missing RLS. Run in the Supabase SQL editor.
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY rowsecurity, tablename;與其他掃描器的比較
大多數通用 DAST 工具 (Burp Suite、OWASP ZAP、Nessus) 不知道 PostgREST 是什麼。它們會爬取你的應用程式、忽略 /rest/v1/ 路徑,並對它們能理解的 HTML 頁面進行回報。Snyk 與 Semgrep 是靜態分析工具 — 它們能在你的儲存庫中找出缺少 RLS 呼叫的移轉檔案,但無法證明已部署的資料庫設定錯誤。FixVibe 處於這個空隙:被動、BaaS 感知、聚焦於未認證攻擊者從公開 URL 可證明的內容。
常見問題
掃描器會讀取或修改我的資料嗎?
The passive RLS check is read-only. It examines observable anonymous read access and reports limited evidence. It does not perform database write operations or prove that every user and tenant is correctly isolated.
如果我的 Supabase 專案已暫停或使用自訂網域,這個還能運作嗎?
已暫停的專案對每個請求都會回傳 503 — 掃描器會回報專案無法連線。只要已部署的應用程式仍會在瀏覽器中載入 Supabase 用戶端 SDK,自訂網域就能運作;無論如何,掃描器都會從套件中擷取專案 URL。
如果我的 anon 金鑰被輪換或可公開金鑰變更了怎麼辦?
重新執行掃描。掃描器在每次執行時都會從目前的套件重新擷取金鑰。輪換只會使先前的報告失效,不會影響資料庫的策略狀態。
掃描器會檢查新的 Supabase 可公開金鑰模型 (sb_publishable_*) 嗎?
會。偵測器會辨識舊的 anon JWT 與較新的 sb_publishable_* 金鑰,並一視同仁地處理 — 兩者都意在公開,且都讓 RLS 成為唯一防線。
後續步驟
Run a free FixVibe scan against your production URL — the Supabase RLS check runs on every plan including the free tier. For a deeper read on what else can leak from a Supabase project, see Supabase service role key exposed in JavaScript and Supabase storage bucket security checklist. For the umbrella view across all BaaS providers, read BaaS misconfiguration scanner.
