// docs / baas security / supabase rls scanner
Supabase RLS 掃描器:找出缺少或損毀的資料列層級安全表
當你部署一個以 Supabase 為後端的應用程式時,資料列層級安全 (RLS) 是站在你客戶資料與網際網路之間的唯一屏障。AI 編碼工具會產生可編譯、上線並悄悄外洩資料的 RLS 形狀程式碼 — 建立時未啟用 RLS 的資料表、可讀卻從不限制的策略、將欄位與自身比較的述詞。本文展示 Supabase RLS 掃描器從外部可以證明什麼、在 vibe 編碼應用中出現的四種 RLS 損毀型態,以及如何在一分鐘內掃描你自己的部署。
外部 RLS 掃描可以證明什麼
被動 RLS 掃描針對 Supabase 在 https://[project].supabase.co/rest/v1/ 暴露的 PostgREST 端點執行。它只使用可公開的 anon 金鑰 — 與你瀏覽器使用的相同金鑰 — 並探測資料表清單中繼資料、匿名讀取與匿名寫入。它永遠不會以使用者身分驗證,也永遠不會接觸服務角色權限。它能做的任何事,網際網路上的未認證攻擊者都能做。
從資料庫外部,掃描器可以高信心地確認下列項目:
- 資料表上 RLS 已停用。當 RLS 關閉或策略允許時,PostgREST 會為匿名
SELECT傳回資料列。任一情況都是一項發現。 - 匿名角色可以列出資料表。使用 anon 金鑰的
GET /rest/v1/會傳回anon角色擁有任何權限的每張資料表的 OpenAPI 結構。AI 生成的應用程式經常授予結構描述的USAGE與每張資料表的SELECT,即使 RLS 拒絕實際讀取,也會暴露完整的結構描述地圖。 - 匿名角色可以插入。如果 RLS 沒有拒絕
INSERT的策略,猜測欄位形狀的探測性POST將會成功 — 即使SELECT已被鎖定。 - 服務角色金鑰在瀏覽器套件中。與 RLS 相鄰:如果掃描器在 JavaScript 套件中找到
SUPABASE_SERVICE_ROLE_KEY或任何帶有role: service_role的 JWT,RLS 就形同虛設 — 該金鑰持有者可以繞過所有策略。
外部掃描無法證明什麼
請誠實面對掃描器的邊界。外部 RLS 掃描無法讀取你的 pg_policies 資料表、移轉檔案或任何策略的確切述詞。它從黑盒行為推論,這代表它有時會回報最終被證實為刻意公開資料 (行銷電子報資料表、公開產品目錄) 的 發現。當掃描器無法消除意圖歧義時,FixVibe 報告會將其標示為中等信心 — 請審視資料表名稱並自行判斷。
AI 工具產生的四種 RLS 損毀型態
當你將 Cursor、Claude Code、Lovable 或 Bolt 指向 Supabase 時,數千個應用程式中會出現相同的四種 RLS 損毀模式。每一種都會通過型別檢查、編譯並上線:
型態 1:從未啟用 RLS
最常見的失敗模式。移轉建立了資料表,但開發者 (或 AI 工具) 忘記 ALTER TABLE ... ENABLE ROW LEVEL SECURITY。PostgREST 愉快地將整張資料表提供給任何持有 anon 金鑰的人。修復: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;。FORCE 是必要的 — 沒有它,資料表擁有者 (以及任何擁有資料表所有權的角色) 會繞過 RLS。
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE 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:策略將欄位與自身比較
A copy-paste artefact. The developer writes <code>USING (user_id = user_id)</code> — which is always true — instead of <code>USING (auth.uid() = user_id)</code>. Type-checks pass; the policy permits every row. <strong>Fix:</strong> always compare a column to a function call (<code>auth.uid()</code>, <code>auth.jwt()->>'org_id'</code>, etc.), never to itself or to a constant.
型態 4:SELECT 上有策略但 INSERT/UPDATE 沒有
開發者鎖定讀取,卻忘了寫入。RLS 策略是依命令逐一套用的。FOR SELECT 只保護讀取;如果沒有策略拒絕,匿名用戶端仍可 INSERT。修復:為每個命令撰寫策略,或使用帶有明確 USING 與 WITH CHECK 子句的 FOR ALL。
FixVibe Supabase RLS 掃描器的運作方式
baas.supabase-rls 檢查分三個階段執行,每個階段都有明確的信心等級:
- 階段 1 — 指紋識別。掃描器爬取已部署的應用程式、解析其 JavaScript 套件,並從執行時設定中擷取 Supabase 專案 URL 與 anon 金鑰。沒有 DNS 猜測,沒有暴力破解 — 它讀取瀏覽器讀取的內容。
- 階段 2 — 結構描述探索。使用 anon 金鑰的單一
GET /rest/v1/會傳回 anon 角色能看到的每張資料表的 OpenAPI 結構描述。此階段掃描器記錄資料表名稱,但不讀取列資料。 - 階段 3 — 讀寫探測。對每張探索到的資料表,掃描器發出一次帶有
limit=1的匿名SELECT。如果傳回資料列,則 RLS 是寬鬆的。掃描器就此停止 — 不列舉列、不分頁、不修改資料。INSERT 探測受驗證的網域所有權與明確的選擇加入所限制;它們永遠不會針對未驗證的目標觸發。
每項發現都附帶確切的請求 URL、回應狀態、回應形狀 (僅標頭) 與資料表名稱。發現底部的 AI 修復提示是可複製貼上的 SQL 區塊,你可在 Supabase SQL 編輯器中執行。
掃描器發現問題時該怎麼做
每一項 RLS 發現都是執行時的緊急事件。公開的 PostgREST 端點會在幾分鐘內被攻擊者掃描。修復順序是機械化的:
- 稽核每張資料表。在 Supabase SQL 編輯器中執行
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';。任何rowsecurity = false的列都是問題。 - 在每張公開資料表上啟用 RLS。對建立的每張資料表預設使用
ENABLE ROW LEVEL SECURITY與FORCE ROW LEVEL SECURITY— 將其作為移轉範本。 - 逐一命令撰寫策略。不要使用
FOR ALL USING (true)。為 SELECT、INSERT、UPDATE、DELETE 各寫明確策略 — 每個都以auth.uid()或來自auth.jwt()的組織 ID 欄位限定範圍。 - 用第二個帳號驗證。註冊為不同使用者,嘗試透過 REST API 直接讀取另一名使用者的記錄。如果回應是
200,則策略已損毀。 - 重新掃描。套用修復後,對同一 URL 重新執行 FixVibe 掃描。
baas.supabase-rls發現應該會被清除。
-- 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 可證明的內容。
常見問題
掃描器會讀取或修改我的資料嗎?
不會。被動掃描對每張探索到的資料表最多發出一次 SELECT ... limit=1,以確認 RLS 是否允許匿名讀取。掃描器記錄回應形狀,而非列內容。INSERT、UPDATE 與 DELETE 探測受驗證的網域所有權所限制,並且永遠不會針對未驗證的目標執行。
如果我的 Supabase 專案已暫停或使用自訂網域,這個還能運作嗎?
已暫停的專案對每個請求都會回傳 503 — 掃描器會回報專案無法連線。只要已部署的應用程式仍會在瀏覽器中載入 Supabase 用戶端 SDK,自訂網域就能運作;無論如何,掃描器都會從套件中擷取專案 URL。
如果我的 anon 金鑰被輪換或可公開金鑰變更了怎麼辦?
重新執行掃描。掃描器在每次執行時都會從目前的套件重新擷取金鑰。輪換只會使先前的報告失效,不會影響資料庫的策略狀態。
掃描器會檢查新的 Supabase 可公開金鑰模型 (<code>sb_publishable_*</code>) 嗎?
會。偵測器會辨識舊的 anon JWT 與較新的 sb_publishable_* 金鑰,並一視同仁地處理 — 兩者都意在公開,且都讓 RLS 成為唯一防線。
後續步驟
對你的生產 URL 執行免費的 FixVibe 掃描 — baas.supabase-rls 檢查在每個方案 (包括免費方案) 上都已啟用。如需深入了解 Supabase 專案還可能洩漏什麼,請參閱 暴露在 JavaScript 中的 Supabase 服務角色金鑰 與 Supabase 儲存桶安全清單。對於所有 BaaS 提供者的總覽,請閱讀 BaaS 設定錯誤掃描器。
