// docs / baas security / supabase rls scanner
Supabase RLS-skanner: finn tabeller med manglende eller ødelagt row-level security
Row-level security (RLS) er det eneste som står mellom kundenes data og internett når du lanserer en Supabase-basert app. AI-kodeverktøy genererer RLS-formet kode som kompilerer, leveres og lekker data i stillhet — tabeller opprettet uten RLS aktivert, policyer som leser men aldri begrenser, predikater som sammenligner en kolonne med seg selv. Denne artikkelen viser hva en Supabase RLS-skanner kan bevise utenfra, de fire formene av ødelagt RLS som dukker opp i vibe-kodede apper, og hvordan du skanner din egen deployment på under et minutt.
Hva en ekstern RLS-skanning kan bevise
En passiv RLS-skanning kjører mot PostgREST-endepunktet som Supabase eksponerer på https://[project].supabase.co/rest/v1/. Den bruker bare den publiserbare anon-nøkkelen — samme nøkkel som nettleseren din bruker — og sonderer etter tabellisten-metadata, anonyme lesinger og anonyme skrivinger. Den autentiserer seg aldri som en bruker og rører aldri service-role-privilegier. Alt den kan gjøre, kan en uautentisert angriper på internett også gjøre.
Utenfra databasen kan en skanner bekrefte følgende med høy konfidens:
- RLS er deaktivert på en tabell. PostgREST returnerer rader for en anonym
SELECTnår RLS er av eller når en policy tillater det. Begge tilfellene er et funn. - Den anonyme rollen kan liste tabeller. En
GET /rest/v1/med anon-nøkkelen returnerer OpenAPI-skjemaet for hver tabell som rollenanonhar noe privilegium på. AI-genererte apper gir ofteUSAGEpå skjemaet ogSELECTpå hver tabell, noe som eksponerer hele skjemakartet selv om RLS nekter selve lesingene. - Den anonyme rollen kan utføre INSERT. En sonderende
POSTmed en gjetning på kolonneformen lykkes hvis RLS ikke har enINSERT-policy som nekter det — selv omSELECTer låst. - Service-role-nøkkelen ligger i nettleserbundlen. Tilstøtende RLS: hvis en skanner finner
SUPABASE_SERVICE_ROLE_KEYeller en hvilken som helst JWT medrole: service_rolei JavaScript-bundlen, er RLS irrelevant — den som har nøkkelen omgår enhver policy.
Hva en ekstern skanning ikke kan bevise
Vær ærlig om skannerens grenser. En ekstern RLS-skanning kan ikke lese pg_policies-tabellen din, migrasjonsfilene dine eller det eksakte predikatet for noen policy. Den slutter seg fra black-box-atferd, noe som betyr at den noen ganger vil rapportere et funn som viser seg å være tilsiktet offentlig data (en markedsføringsnyhetsbrev-tabell, en offentlig produktkatalog). FixVibe-rapporten flagger disse som middels konfidens når skanneren ikke kan skille intensjonen — gjennomgå tabellnavnet og avgjør.
De fire formene av ødelagt RLS som AI-verktøy produserer
Når du retter Cursor, Claude Code, Lovable eller Bolt mot Supabase, dukker de samme fire mønstrene for ødelagt RLS opp på tvers av tusenvis av apper. Hvert av dem består typekontroll, kompilerer og leveres:
Form 1: RLS aldri aktivert
Den vanligste feilmodusen. Migrasjonen oppretter tabellen, men utvikleren (eller AI-verktøyet) glemmer ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST serverer glade i livet hele tabellen til enhver med anon-nøkkelen. Fiks: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE er ikke valgfritt — uten det omgår tabelleieren (og enhver rolle med tabelleierskap) RLS.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;Form 2: RLS aktivert, ingen policyer
En mer subtil feil. RLS er aktivert, men ingen policyer er skrevet. Standarden i PostgreSQL er nekt, så autentiserte brukere ser ingenting — og utvikleren legger til USING (true) for å få appen til å fungere, noe som lar alle lese alt. Fiks: skriv en policy som avgrenses via auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); og en matchende INSERT/UPDATE/DELETE-policy.
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);Form 3: Policy sammenligner kolonne med seg 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: Policy på SELECT, men ikke på INSERT/UPDATE
Utvikleren låser lesinger, men glemmer skrivinger. RLS-policyer er per kommando. FOR SELECT beskytter bare lesinger; en anonym klient kan fortsatt INSERT hvis ingen policy nekter det. Fiks: skriv en policy per kommando, eller bruk FOR ALL med eksplisitte USING- og WITH CHECK-klausuler.
Slik fungerer FixVibes Supabase RLS-skanner
baas.supabase-rls-sjekken kjører i tre trinn, hvert med eksplisitte konfidensnivåer:
- Trinn 1 — fingeravtrykking. Skanneren crawler den deployede appen, parser dens JavaScript-bundle og henter ut Supabase-prosjektets URL og anon-nøkkel fra runtime-konfigurasjonen. Ingen DNS-gjetting, ingen brute force — den leser det nettleseren leser.
- Trinn 2 — skjemaoppdagelse. En enkelt
GET /rest/v1/med anon-nøkkelen returnerer OpenAPI-skjemaet for hver tabell anon-rollen kan se. Skanneren noterer tabellnavn, men leser ingen raddata i dette trinnet. - Trinn 3 — lese- og skrivesonderinger. For hver oppdaget tabell utsteder skanneren en anonym
SELECTmedlimit=1. Hvis rader returneres, er RLS tillatende. Skanneren stopper der — den teller ikke rader, paginerer ikke, modifiserer ikke data. INSERT-sonderinger er begrenset bak verifisert domeneierskap og eksplisitt opt-in; de kjøres aldri mot uverifiserte mål.
Hvert funn leveres med den eksakte request-URL-en, response-statusen, response-formen (kun header) og tabellnavnet. AI-fiksprompten nederst i funnet er en copy-paste-SQL-blokk som du kjører i Supabase SQL-editoren.
Hva du gjør når skanneren finner noe
Hvert RLS-funn er en runtime-nødssituasjon. Offentlige PostgREST-endepunkter blir skannet av angripere innen minutter. Utbedringssekvensen er mekanisk:
- Granske hver tabell. Kjør
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';i Supabase SQL-editoren. Hver rad medrowsecurity = falseer et problem. - Aktivere RLS på hver offentlige tabell. Standard til
ENABLE ROW LEVEL SECURITYogFORCE ROW LEVEL SECURITYpå hver tabell som opprettes — gjør det til en migrasjonsmal. - Skrive policyer kommando-for-kommando. Ikke bruk
FOR ALL USING (true). Skriv eksplisitte policyer for SELECT, INSERT, UPDATE, DELETE — hver av dem avgrenset tilauth.uid()eller en org-id-kolonne fraauth.jwt(). - Verifisere med en annen konto. Registrer deg som en annen bruker, forsøk å lese en annen brukers data direkte via REST-API-et. Hvis svaret er
200, er policyen ødelagt. - Skanne på nytt. Etter at fiksen er anvendt, kjør en FixVibe-skanning mot samme URL på nytt.
baas.supabase-rls-funnet skal forsvinne.
-- 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 skannere
De fleste generiske DAST-verktøy (Burp Suite, OWASP ZAP, Nessus) vet ikke hva PostgREST er. De crawler appen din, ignorerer /rest/v1/-stien og rapporterer på HTML-sidene de forstår. Snyk og Semgrep er statisk-analyse-verktøy — de finner migrasjonsfiler i repoet ditt med manglende RLS-kall, men de kan ikke bevise at den deployede databasen er feilkonfigurert. FixVibe sitter i gapet: passiv, BaaS-bevisst, fokusert på det en uautentisert angriper kan bevise fra den offentlige URL-en.
Ofte stilte spørsmål
Vil skanneren lese eller modifisere dataene mine?
Nei. Passive skanninger utsteder maksimalt én SELECT ... limit=1 per oppdaget tabell for å bekrefte om RLS tillater anonyme lesinger. Skanneren noterer response-formen, ikke radinnholdet. INSERT-, UPDATE- og DELETE-sonderinger er begrenset bak verifisert domeneierskap og kjøres aldri mot uverifiserte mål.
Fungerer dette hvis Supabase-prosjektet mitt er pauset eller på et tilpasset domene?
Pausede prosjekter returnerer 503 på hver request — skanneren rapporterer prosjektet som utilgjengelig. Tilpassede domener fungerer så lenge den deployede appen fortsatt laster Supabase-klient-SDK-en i nettleseren; skanneren henter prosjekt-URL-en fra bundlen uansett.
Hva hvis anon-nøkkelen min roteres eller den publiserbare nøkkelen min endres?
Kjør skanningen på nytt. Skanneren henter nøkkelen på nytt fra den nåværende bundlen ved hver kjøring. Rotasjon invaliderer kun den forrige rapporten, ikke databasens policy-tilstand.
Sjekker skanneren den nye Supabase publiserbare-nøkkel-modellen (<code>sb_publishable_*</code>)?
Ja. Detektoren gjenkjenner både eldre anon-JWT-er og de nyere sb_publishable_*-nøklene og behandler dem identisk — begge er ment å være offentlige, og begge lar RLS være den eneste forsvarslinjen.
Neste steg
Kjør en gratis FixVibe-skanning mot produksjons-URL-en din — baas.supabase-rls-sjekken er aktivert på alle planer, inkludert gratisnivået. For en dypere lesning av hva annet som kan lekke fra et Supabase-prosjekt, se Supabase service-role-nøkkel eksponert i JavaScript og Sjekkliste for Supabase storage-bucket-sikkerhet. For helhetsbildet på tvers av alle BaaS-leverandører, les BaaS-feilkonfigurasjonsskanner.
