// docs / baas security / supabase rls scanner
Supabase RLS-scanner: find tabeller med manglende eller defekt row-level security
Row-level security (RLS) er det eneste, der står mellem dine kunders data og internettet, når du udgiver en Supabase-baseret app. AI-kodeværktøjer genererer RLS-formet kode, der kompilerer, leveres og lydløst lækker data — tabeller oprettet uden RLS aktiveret, politikker der læser men aldrig begrænser, prædikater der sammenligner en kolonne med sig selv. Denne artikel viser, hvad en Supabase RLS-scanner kan bevise udefra, de fire defekte RLS-former, der dukker op i vibe-kodede apps, og hvordan du scanner din egen deployment på under et minut.
Hvad en ekstern RLS-scanning kan bevise
En passiv RLS-scanning køres mod PostgREST-endpointen, som Supabase eksponerer på https://[project].supabase.co/rest/v1/. Den bruger kun den udgivelige anon-nøgle — den samme nøgle, som din browser bruger — og sonderer tabellistemetadata, anonyme læsninger og anonyme skrivninger. Den autentificerer sig aldrig som bruger og rører aldrig service-role-privilegier. Alt, hvad den kan gøre, kan en uautentificeret angriber på internettet også gøre.
Udefra databasen kan en scanner bekræfte følgende med høj konfidens:
- RLS er deaktiveret på en tabel. PostgREST returnerer rækker for et anonymt
SELECT, når RLS er slået fra, eller når en politik tillader det. Begge tilfælde er et fund. - Den anonyme rolle kan liste tabeller. Et
GET /rest/v1/med anon-nøglen returnerer OpenAPI-skemaet for hver tabel, som rollenanonhar en eller anden rettighed til. AI-genererede apps tildeler ofteUSAGEpå skemaet ogSELECTpå hver tabel, hvilket eksponerer hele skemakortet, selv når RLS afviser de faktiske læsninger. - Den anonyme rolle kan indsætte. En sonderende
POSTmed et gæt på kolonneformen lykkes, hvis RLS ikke har enINSERT-politik, der afviser den — selv hvisSELECTer låst. - Service-role-nøglen er i browserbundtet. Tilstødende RLS: hvis en scanner finder
SUPABASE_SERVICE_ROLE_KEYeller en JWT medrole: service_rolei JavaScript-bundtet, er RLS irrelevant — den, der har nøglen, omgår enhver politik.
Hvad en ekstern scanning ikke kan bevise
Vær ærlig om scannerens grænser. En ekstern RLS-scanning kan ikke læse din pg_policies-tabel, dine migrationsfiler eller det nøjagtige prædikat for nogen politik. Den udleder fra black-box-adfærd, hvilket betyder, at den nogle gange rapporterer et fund, der viser sig at være tilsigtede offentlige data (en marketingnyhedsbrev-tabel, et offentligt produktkatalog). FixVibe-rapporten markerer disse som medium konfidens, når scanneren ikke kan adskille hensigt — gennemgå tabelnavnet og afgør.
De fire defekte RLS-former, som AI-værktøjer producerer
Når du retter Cursor, Claude Code, Lovable eller Bolt mod Supabase, dukker de samme fire defekte RLS-mønstre op på tværs af tusindvis af apps. Hver enkelt består typekontrol, kompilerer og leveres:
Form 1: RLS aldrig aktiveret
Den mest almindelige fejltilstand. Migrationen opretter tabellen, men udvikleren (eller AI-værktøjet) glemmer ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST serverer glædeligt hele tabellen til alle med anon-nøglen. Fix: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE er ikke valgfrit — uden det omgår tabelejeren (og enhver rolle med tabelejerskab) RLS.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;Form 2: RLS aktiveret, ingen politikker
En mere subtil fejl. RLS er aktiveret, men ingen politikker er skrevet. Standarden i PostgreSQL er afvis, så autentificerede brugere ser intet — og udvikleren tilføjer USING (true) for at få appen til at fungere, hvilket tillader alle at læse alt. Fix: skriv en politik, der begrænses af auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); og en matchende INSERT/UPDATE/DELETE-politik.
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);Form 3: Politik sammenligner kolonne med sig selv
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: Politik på SELECT, men ikke på INSERT/UPDATE
Udvikleren låser læsninger, men glemmer skrivninger. RLS-politikker er per kommando. FOR SELECT beskytter kun læsninger; en anonym klient kan stadig INSERT, hvis ingen politik afviser det. Fix: skriv en politik per kommando, eller brug FOR ALL med eksplicitte USING- og WITH CHECK-klausuler.
Sådan fungerer FixVibes Supabase RLS-scanner
baas.supabase-rls-checken kører i tre trin, hver med eksplicitte konfidensniveauer:
- Trin 1 — fingeraftryk. Scanneren crawler den deployede app, parser dens JavaScript-bundt og udtrækker Supabase-projektets URL og anon-nøgle fra runtime-konfigurationen. Ingen DNS-gætteri, ingen brute force — den læser det, browseren læser.
- Trin 2 — skemaopdagelse. Et enkelt
GET /rest/v1/med anon-nøglen returnerer OpenAPI-skemaet for hver tabel, som anon-rollen kan se. Scanneren registrerer tabelnavne, men læser ingen rækkedata i dette trin. - Trin 3 — læse- og skrivesonderinger. For hver opdaget tabel udsteder scanneren ét anonymt
SELECTmedlimit=1. Hvis rækker returneres, er RLS tilladende. Scanneren stopper der — den opregner ikke rækker, paginerer ikke, ændrer ingen data. INSERT-sonderinger er begrænset bag verificeret domæneejerskab og eksplicit opt-in; de kører aldrig mod uverificerede mål.
Hvert fund leveres med den nøjagtige request-URL, response-status, response-form (kun header) og tabelnavnet. AI-fix-prompten i bunden af fundet er en copy-paste-SQL-blok, som du kører i Supabase SQL-editoren.
Hvad du gør, når scanneren finder noget
Hvert RLS-fund er en runtime-nødsituation. Offentlige PostgREST-endpoints scannes af angribere inden for minutter. Afhjælpningssekvensen er mekanisk:
- Revider hver tabel. Kør
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';i Supabase SQL-editoren. Enhver række medrowsecurity = falseer et problem. - Aktiver RLS på hver offentlig tabel. Brug som standard
ENABLE ROW LEVEL SECURITYogFORCE ROW LEVEL SECURITYpå hver oprettet tabel — gør det til en migrationsskabelon. - Forfat politikker kommando for kommando. Brug ikke
FOR ALL USING (true). Skriv eksplicitte politikker for SELECT, INSERT, UPDATE, DELETE — hver begrænset tilauth.uid()eller en org-id-kolonne fraauth.jwt(). - Verificer med en anden konto. Tilmeld dig som en anden bruger, forsøg at læse en anden brugers poster direkte via REST-API'en. Hvis svaret er
200, er politikken defekt. - Scan igen. Efter at have anvendt fixen, kør en FixVibe-scanning igen mod den samme URL.
baas.supabase-rls-fundet bør forsvinde.
-- 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;Hvordan dette sammenlignes med andre scannere
De fleste generiske DAST-værktøjer (Burp Suite, OWASP ZAP, Nessus) ved ikke, hvad PostgREST er. De crawler din app, ignorerer /rest/v1/-stien og rapporterer om de HTML-sider, de forstår. Snyk og Semgrep er statiske analyseværktøjer — de finder migrationsfiler i dit repo med manglende RLS-kald, men de kan ikke bevise, at den deployede database er fejlkonfigureret. FixVibe sidder i hullet: passiv, BaaS-bevidst, fokuseret på, hvad en uautentificeret angriber kan bevise udefra den offentlige URL.
Ofte stillede spørgsmål
Læser eller modificerer scanneren mine data?
Nej. Passive scanninger udsteder højst ét SELECT ... limit=1 per opdaget tabel for at bekræfte, om RLS tillader anonyme læsninger. Scanneren registrerer response-formen, ikke rækkernes indhold. INSERT-, UPDATE- og DELETE-sonderinger er begrænset bag verificeret domæneejerskab og kører aldrig mod uverificerede mål.
Fungerer det, hvis mit Supabase-projekt er sat på pause eller på et brugerdefineret domæne?
Pausede projekter returnerer 503 på hver forespørgsel — scanneren rapporterer projektet som utilgængeligt. Brugerdefinerede domæner fungerer, så længe den deployede app stadig indlæser Supabase-klient-SDK'en i browseren; scanneren udtrækker projekt-URL'en fra bundtet under alle omstændigheder.
Hvad nu hvis min anon-nøgle roteres, eller min publicerbare nøgle ændres?
Kør scanningen igen. Scanneren udtrækker nøglen fra det aktuelle bundt på hver kørsel. Rotation ugyldiggør kun den tidligere rapport, ikke databasens politiktilstand.
Tjekker scanneren den nye Supabase publicerbare-nøgle-model (<code>sb_publishable_*</code>)?
Ja. Detektoren genkender både ældre anon-JWT'er og de nyere sb_publishable_*-nøgler og behandler dem identisk — begge er beregnet til at være offentlige og efterlader begge RLS som den eneste forsvarslinje.
Næste skridt
Kør en gratis FixVibe-scanning mod din produktions-URL — baas.supabase-rls-checken er aktiveret på alle planer inklusive gratisniveauet. For en dybere læsning af, hvad der ellers kan lække fra et Supabase-projekt, se Supabase service-role-nøgle eksponeret i JavaScript og Supabase storage-bucket sikkerhedstjekliste. For paraplyvisningen på tværs af alle BaaS-udbydere, læs BaaS-fejlkonfigurationsscanner.
