// docs / baas security / supabase rls scanner
Supabase RLS-scanner: hitta tabeller med saknad eller trasig row-level security
Row-level security (RLS) är det enda som står mellan dina kunders data och internet när du släpper en Supabase-baserad app. AI-kodningsverktyg genererar RLS-format kod som kompilerar, levereras och tyst läcker data — tabeller skapade utan RLS aktiverat, policys som läser men aldrig begränsar, predikat som jämför en kolumn med sig själv. Den här artikeln visar vad en Supabase RLS-scanner kan bevisa utifrån, de fyra trasiga RLS-formerna som dyker upp i vibe-kodade appar, och hur du skannar din egen deployment på under en minut.
Vad en extern RLS-scanning kan bevisa
En passiv RLS-scanning körs mot PostgREST-endpointen som Supabase exponerar på https://[project].supabase.co/rest/v1/. Den använder endast den publicerbara anon-nyckeln — samma nyckel som din webbläsare använder — och sonderar tabellist-metadata, anonyma läsningar och anonyma skrivningar. Den autentiserar sig aldrig som en användare och rör aldrig service-role-privilegier. Allt den kan göra kan en oautentiserad angripare på internet också göra.
Utifrån databasen kan en scanner bekräfta följande med hög konfidens:
- RLS är inaktiverat på en tabell. PostgREST returnerar rader för ett anonymt
SELECTnär RLS är av eller när en policy tillåter det. Båda fallen är ett fynd. - Den anonyma rollen kan lista tabeller. En
GET /rest/v1/med anon-nyckeln returnerar OpenAPI-schemat för varje tabell som rollenanonhar något privilegium på. AI-genererade appar beviljar oftaUSAGEpå schemat ochSELECTpå varje tabell, vilket exponerar hela schemakartan även om RLS nekar själva läsningarna. - Den anonyma rollen kan utföra INSERT. En sonderande
POSTmed en gissning om kolumnformen lyckas om RLS inte har enINSERT-policy som nekar det — även omSELECTär låst. - Service-role-nyckeln finns i webbläsarbundlen. Närliggande RLS: om en scanner hittar
SUPABASE_SERVICE_ROLE_KEYeller någon JWT medrole: service_rolei JavaScript-bundlen, är RLS irrelevant — den som har nyckeln kringgår varje policy.
Vad en extern skanning inte kan bevisa
Var ärlig om scannerns gränser. En extern RLS-scanning kan inte läsa din pg_policies-tabell, dina migrationsfiler eller det exakta predikatet för någon policy. Den drar slutsatser från black-box-beteende, vilket innebär att den ibland rapporterar ett fynd som visar sig vara avsiktligt publik data (en marknadsföringsnyhetsbrev-tabell, en publik produktkatalog). FixVibe-rapporten flaggar dessa som medelhög konfidens när scannern inte kan särskilja avsikten — granska tabellnamnet och avgör.
De fyra trasiga RLS-formerna som AI-verktyg producerar
När du riktar Cursor, Claude Code, Lovable eller Bolt mot Supabase dyker samma fyra trasiga RLS-mönster upp över tusentals appar. Var och en passerar typkontroll, kompilerar och levereras:
Form 1: RLS aldrig aktiverat
Det vanligaste felläget. Migrationen skapar tabellen men utvecklaren (eller AI-verktyget) glömmer ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST serverar glatt hela tabellen till alla med anon-nyckeln. Fix: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE är inte valfritt — utan det kringgår tabellägaren (och varje roll med tabellägarskap) RLS.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;Form 2: RLS aktiverat, inga policys
Ett mer subtilt fel. RLS är aktiverat men inga policys är skrivna. Standardvärdet i PostgreSQL är neka, så autentiserade användare ser ingenting — och utvecklaren lägger till USING (true) för att få appen att fungera, vilket tillåter alla att läsa allt. Fix: skriv en policy som begränsar via auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); och en matchande INSERT/UPDATE/DELETE-policy.
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);Form 3: Policy jämför kolumn med sig själv
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.
Form 4: Policy på SELECT men inte på INSERT/UPDATE
Utvecklaren låser läsningar men glömmer skrivningar. RLS-policys är per kommando. FOR SELECT skyddar endast läsningar; en anonym klient kan fortfarande INSERT om ingen policy nekar det. Fix: skriv en policy per kommando, eller använd FOR ALL med explicita USING- och WITH CHECK-klausuler.
Hur FixVibes Supabase RLS-scanner fungerar
baas.supabase-rls-checken körs i tre steg, var och en med explicita konfidensnivåer:
- Steg 1 — fingeravtryckning. Scannern crawlar den deployade appen, parsar dess JavaScript-bundle och extraherar Supabase-projektets URL och anon-nyckel från runtime-konfigurationen. Ingen DNS-gissning, ingen brute force — den läser det webbläsaren läser.
- Steg 2 — schemaupptäckt. En enda
GET /rest/v1/med anon-nyckeln returnerar OpenAPI-schemat för varje tabell som anon-rollen kan se. Scannern noterar tabellnamn men läser ingen raddata i detta steg. - Steg 3 — läs- och skrivsonderingar. För varje upptäckt tabell utfärdar scannern ett anonymt
SELECTmedlimit=1. Om rader returneras är RLS tillåtande. Scannern stannar där — den räknar inga rader, paginerar inte, modifierar ingen data. INSERT-sonderingar är begränsade bakom verifierat domänägarskap och explicit opt-in; de körs aldrig mot overifierade mål.
Varje fynd levereras med den exakta request-URL:en, response-statusen, response-formen (endast header) och tabellnamnet. AI-fixprompten längst ner i fyndet är ett copy-paste-SQL-block som du kör i Supabase SQL-editorn.
Vad du gör när scannern hittar något
Varje RLS-fynd är en runtime-nödsituation. Publika PostgREST-endpoints skannas av angripare inom minuter. Åtgärdssekvensen är mekanisk:
- Granska varje tabell. Kör
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';i Supabase SQL-editorn. Varje rad medrowsecurity = falseär ett problem. - Aktivera RLS på varje publik tabell. Använd som standard
ENABLE ROW LEVEL SECURITYochFORCE ROW LEVEL SECURITYpå varje skapad tabell — gör det till en migrationsmall. - Skriv policys kommando för kommando. Använd inte
FOR ALL USING (true). Skriv explicita policys för SELECT, INSERT, UPDATE, DELETE — var och en begränsad tillauth.uid()eller en org-id-kolumn frånauth.jwt(). - Verifiera med ett andra konto. Registrera dig som en annan användare, försök läsa en annan användares poster direkt via REST-API:t. Om svaret är
200är policyn trasig. - Skanna om. Efter att fixen tillämpats, kör en FixVibe-scanning igen mot samma URL.
baas.supabase-rls-fyndet ska försvinna.
-- 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;Hur detta jämförs med andra scanners
De flesta generiska DAST-verktyg (Burp Suite, OWASP ZAP, Nessus) vet inte vad PostgREST är. De crawlar din app, ignorerar /rest/v1/-sökvägen och rapporterar om HTML-sidorna de förstår. Snyk och Semgrep är statiska analysverktyg — de hittar migrationsfiler i ditt repo med saknade RLS-anrop, men de kan inte bevisa att den deployade databasen är felkonfigurerad. FixVibe sitter i den luckan: passiv, BaaS-medveten, fokuserad på vad en oautentiserad angripare kan bevisa utifrån den publika URL:en.
Vanliga frågor
Läser eller modifierar scannern mina data?
Nej. Passiva skanningar utfärdar som mest ett SELECT ... limit=1 per upptäckt tabell för att bekräfta om RLS tillåter anonyma läsningar. Scannern noterar response-formen, inte radernas innehåll. INSERT-, UPDATE- och DELETE-sonderingar är begränsade bakom verifierat domänägarskap och körs aldrig mot overifierade mål.
Fungerar det om mitt Supabase-projekt är pausat eller på en egen domän?
Pausade projekt returnerar 503 på varje förfrågan — scannern rapporterar projektet som onåbart. Egna domäner fungerar så länge den deployade appen fortfarande laddar Supabase-klient-SDK:t i webbläsaren; scannern extraherar ändå projekt-URL:en från bundlen.
Vad händer om min anon-nyckel roteras eller min publicerbara nyckel ändras?
Kör skanningen igen. Scannern extraherar nyckeln på nytt från den aktuella bundlen vid varje körning. Rotationen invaliderar bara den tidigare rapporten, inte databasens policy-tillstånd.
Kontrollerar scannern den nya Supabase publicerbara-nyckel-modellen (<code>sb_publishable_*</code>)?
Ja. Detektorn känner igen både äldre anon-JWT:er och de nyare sb_publishable_*-nycklarna och behandlar dem identiskt — båda är avsedda att vara publika och båda lämnar RLS som enda försvarslinje.
Nästa steg
Kör en gratis FixVibe-scanning mot din produktions-URL — baas.supabase-rls-checken är aktiverad på varje plan inklusive gratisnivån. För en djupare läsning om vad mer som kan läcka från ett Supabase-projekt, se Supabase service-role-nyckel exponerad i JavaScript och Checklista för Supabase storage-bucket-säkerhet. För helhetsbilden över alla BaaS-leverantörer, läs BaaS-felkonfigurationsscanner.
