FixVibe

// docs / baas security / supabase rls scanner

Supabase RLS-scanner: vind tabellen met ontbrekende of kapotte row-level security

Row-level security (RLS) is het enige dat tussen de gegevens van je klanten en het internet staat wanneer je een Supabase-gebaseerde app lanceert. AI-codeertools genereren RLS-vormige code die compileert, wordt gelanceerd en stilletjes gegevens lekt β€” tabellen die zonder RLS-activatie zijn aangemaakt, beleidsregels die lezen maar nooit beperken, predicaten die een kolom met zichzelf vergelijken. Dit artikel laat zien wat een Supabase RLS-scanner van buitenaf kan bewijzen, de vier kapotte-RLS-vormen die opduiken in vibe-gecodeerde apps, en hoe je je eigen deployment in minder dan een minuut kunt scannen.

Wat een externe RLS-scan kan bewijzen

Een passieve RLS-scan loopt tegen het PostgREST-endpoint dat Supabase blootstelt op https://[project].supabase.co/rest/v1/. Het gebruikt alleen de publiceerbare anon-sleutel β€” dezelfde sleutel die je browser gebruikt β€” en sondeert op tabellijst-metadata, anonieme reads en anonieme writes. Het authenticeert nooit als een gebruiker en raakt nooit service-rolprivileges aan. Alles wat het kan doen, kan een niet-geauthenticeerde aanvaller op het internet ook doen.

Van buiten de database kan een scanner het volgende met hoog vertrouwen bevestigen:

  • RLS is uitgeschakeld op een tabel. PostgREST retourneert rijen voor een anonieme SELECT wanneer RLS uitstaat of wanneer een beleid het toestaat. Beide gevallen zijn een bevinding.
  • De anonieme rol kan tabellen oplijsten. Een GET /rest/v1/ met de anon-sleutel retourneert het OpenAPI-schema voor elke tabel waarop de anon-rol enig privilege heeft. AI-gegenereerde apps verlenen vaak USAGE op het schema en SELECT op elke tabel, wat de volledige schema-kaart blootstelt, zelfs wanneer RLS de feitelijke reads weigert.
  • De anonieme rol kan invoegen. Een sonderende POST met een gok naar de kolomvorm slaagt als RLS geen INSERT-beleid heeft dat het weigert β€” zelfs als SELECT vergrendeld is.
  • De service-rolsleutel staat in de browserbundle. Aangrenzend aan RLS: als een scanner SUPABASE_SERVICE_ROLE_KEY of een JWT met role: service_role in de JavaScript-bundle vindt, is RLS irrelevant β€” de houder van die sleutel omzeilt elk beleid.

Wat een externe scan niet kan bewijzen

Wees eerlijk over de grenzen van de scanner. Een externe RLS-scan kan je pg_policies-tabel, je migratiebestanden of het exacte predicaat van een beleid niet lezen. Het maakt gevolgtrekkingen uit black-box-gedrag, wat betekent dat het soms een bevinding rapporteert die opzettelijk publieke gegevens blijkt te zijn (een marketingnieuwsbrieftabel, een openbare productcatalogus). Het FixVibe-rapport markeert deze als gemiddeld vertrouwen wanneer de scanner de intentie niet kan onderscheiden β€” bekijk de tabelnaam en beslis.

De vier kapotte-RLS-vormen die AI-tools produceren

Wanneer je Cursor, Claude Code, Lovable of Bolt op Supabase richt, ontstaan dezelfde vier kapotte-RLS-patronen in duizenden apps. Elk slaagt voor type-check, compileert en wordt gelanceerd:

Vorm 1: RLS is nooit ingeschakeld

De meest voorkomende faalmodus. De migratie maakt de tabel aan, maar de ontwikkelaar (of de AI-tool) vergeet ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST serveert vrolijk de hele tabel aan iedereen met de anon-sleutel. Fix: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE is niet optioneel β€” zonder dit omzeilt de tabeleigenaar (en elke rol met tabeleigenaarschap) RLS.

sql
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE  ROW LEVEL SECURITY;

Vorm 2: RLS ingeschakeld, geen beleidsregels

Een subtielere mislukking. RLS is ingeschakeld, maar er zijn geen beleidsregels geschreven. De standaard in PostgreSQL is weigeren, dus geauthenticeerde gebruikers zien niets β€” en de ontwikkelaar voegt USING (true) toe om de app werkend te krijgen, wat iedereen toestaat alles te lezen. Fix: schrijf een beleid dat scoopt op auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); en een bijpassend INSERT/UPDATE/DELETE-beleid.

sql
CREATE POLICY "select_own"
  ON public.[name]
  FOR SELECT
  USING (auth.uid() = user_id);

Vorm 3: Beleid vergelijkt kolom met zichzelf

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.

Vorm 4: Beleid op SELECT maar niet op INSERT/UPDATE

De ontwikkelaar vergrendelt reads maar vergeet writes. RLS-beleidsregels zijn per-opdracht. FOR SELECT beschermt alleen reads; een anonieme client kan nog steeds INSERTen als geen beleid het weigert. Fix: schrijf een beleid per opdracht of gebruik FOR ALL met expliciete USING- en WITH CHECK-clausules.

Hoe de FixVibe Supabase RLS-scanner werkt

De baas.supabase-rls-check loopt in drie fasen, elk met expliciete vertrouwensniveaus:

  1. Fase 1 β€” fingerprinten. De scanner crawlt de gedeployde app, parseert de JavaScript-bundle en extraheert de Supabase-project-URL en anon-sleutel uit de runtimeconfiguratie. Geen DNS-gokken, geen brute force β€” het leest wat de browser leest.
  2. Fase 2 β€” schema-ontdekking. Een enkele GET /rest/v1/ met de anon-sleutel retourneert het OpenAPI-schema voor elke tabel die de anon-rol kan zien. De scanner registreert tabelnamen maar leest in dit stadium geen rijgegevens.
  3. Fase 3 β€” lees- en schrijfsondes. Voor elke ontdekte tabel geeft de scanner één anonieme SELECT uit met limit=1. Als rijen worden geretourneerd, is RLS toegestaan. De scanner stopt daar β€” hij somt geen rijen op, pagineert niet, wijzigt geen gegevens. INSERT-sondes zijn afgeschermd achter geverifieerd domeineigenaarschap en expliciete opt-in; ze worden nooit afgevuurd tegen ongeverifieerde doelen.

Elke bevinding wordt geleverd met de exacte aanvraag-URL, antwoordstatus, antwoordvorm (alleen header) en de tabelnaam. De AI-fixprompt onderaan de bevinding is een copy-paste SQL-blok dat je uitvoert in de Supabase SQL-editor.

Wat te doen wanneer de scanner iets vindt

Elke RLS-bevinding is een runtime-noodsituatie. Openbare PostgREST-endpoints worden binnen enkele minuten door aanvallers gescand. De herstelsequentie is mechanisch:

  1. Audit elke tabel. Voer SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public'; uit in de Supabase SQL-editor. Elke rij met rowsecurity = false is een probleem.
  2. Schakel RLS in op elke openbare tabel. Maak ENABLE ROW LEVEL SECURITY en FORCE ROW LEVEL SECURITY standaard op elke aangemaakte tabel β€” maak er een migratiesjabloon van.
  3. Schrijf beleidsregels per opdracht. Gebruik geen FOR ALL USING (true). Schrijf expliciete beleidsregels voor SELECT, INSERT, UPDATE, DELETE β€” elk gescoopt op auth.uid() of een org-id-kolom uit auth.jwt().
  4. Verifieer met een tweede account. Meld je aan als een andere gebruiker, probeer de records van een andere gebruiker rechtstreeks via de REST API te lezen. Als het antwoord 200 is, is het beleid kapot.
  5. Hersannen. Voer na het toepassen van de fix opnieuw een FixVibe-scan uit tegen dezelfde URL. De baas.supabase-rls-bevinding zou moeten verdwijnen.
sql
-- 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;

Hoe dit zich verhoudt tot andere scanners

De meeste generieke DAST-tools (Burp Suite, OWASP ZAP, Nessus) weten niet wat PostgREST is. Ze crawlen je app, negeren het /rest/v1/-pad en rapporteren over de HTML-pagina's die ze wel begrijpen. Snyk en Semgrep zijn statische-analysetools β€” ze vinden migratiebestanden in je repo met ontbrekende RLS-aanroepen, maar kunnen niet bewijzen dat de gedeployde database verkeerd is geconfigureerd. FixVibe zit in de kloof: passief, BaaS-bewust, gericht op wat een niet-geauthenticeerde aanvaller vanaf de openbare URL kan bewijzen.

Veelgestelde vragen

Leest of wijzigt de scanner mijn gegevens?

Nee. Passieve scans geven hooguit één SELECT ... limit=1 uit per ontdekte tabel om te bevestigen of RLS anonieme reads toestaat. De scanner registreert de antwoordvorm, niet de rij-inhoud. INSERT-, UPDATE- en DELETE-sondes zijn afgeschermd achter geverifieerd domeineigenaarschap en lopen nooit tegen ongeverifieerde doelen.

Werkt dit als mijn Supabase-project gepauzeerd is of op een aangepast domein staat?

Gepauzeerde projecten retourneren 503 op elke aanvraag β€” de scanner rapporteert het project als onbereikbaar. Aangepaste domeinen werken zolang de gedeployde app nog steeds de Supabase-client-SDK in de browser laadt; de scanner extraheert de project-URL hoe dan ook uit de bundle.

Wat als mijn anon-sleutel wordt geroteerd of mijn publiceerbare sleutel verandert?

Voer de scan opnieuw uit. De scanner extraheert de sleutel bij elke run opnieuw uit de huidige bundle. Rotatie maakt alleen het vorige rapport ongeldig, niet de beleidsstatus van de database.

Controleert de scanner het nieuwe Supabase-publiceerbare-sleutelmodel (<code>sb_publishable_*</code>)?

Ja. De detector herkent zowel legacy anon-JWT's als de nieuwere sb_publishable_*-sleutels en behandelt ze identiek β€” beide zijn bedoeld om openbaar te zijn en beide laten RLS over als de enige verdedigingslinie.

Volgende stappen

Voer een gratis FixVibe-scan uit tegen je productie-URL β€” de baas.supabase-rls-check is ingeschakeld op elk plan, inclusief de gratis tier. Voor een diepere blik op wat er nog kan lekken uit een Supabase-project, zie Supabase service-rolsleutel blootgesteld in JavaScript en Supabase opslagbucket-beveiligingschecklist. Voor het paraplu-overzicht over alle BaaS-providers, lees BaaS-misconfiguratiescanner.

// scan je baas-oppervlak

Vind de open tabel voordat iemand anders dat doet.

Voer een productie-URL in. FixVibe somt de BaaS-providers op waarmee je app communiceert, fingerprint hun openbare endpoints en rapporteert wat een niet-geauthenticeerde client kan lezen of schrijven. Gratis, geen installatie, geen kaart.

  • Gratis tier β€” 3 scans / maand, geen kaart bij aanmelden.
  • Passieve BaaS-fingerprinting β€” geen domeinverificatie nodig.
  • Supabase, Firebase, Clerk, Auth0, Appwrite en meer.
  • AI-fixprompts bij elke bevinding β€” plak terug in Cursor / Claude Code.
Supabase RLS-scanner: vind tabellen met ontbrekende of kapotte row-level security β€” Docs Β· FixVibe