Database Security
Postgres, Supabase og RLS: når anon-nøkkelen ligger i browseren er policies din siste forsvarslinje.
Modellen
I Supabase-lignende arkitektur snakker browseren direkte med databasen via en publishable/anon key. Den nøkkelen er offentlig. Det eneste som skiller brukerne er Row Level Security.
service_role bypasser RLS. Den skal kun brukes på server, og kun etter at du har verifisert kalleren.
- ›Er RLS aktivert på ALLE tabeller i public?
- ›Har anon-rollen grants/policies den ikke trenger?
- ›Er policies like strenge for INSERT/UPDATE/DELETE som for SELECT?
- ›Har UPDATE både USING og WITH CHECK?
- ›Tenant isolation: sjekkes org_id via medlemskap, ikke fra klientverdi?
Feil policy: alle innloggede kan lese alt
AI genererer ofte «using (true)» for å få appen til å fungere. Det gir alle autentiserte brukere tilgang til alle rader.
alter table documents enable row level security; create policy "read" on documents for select to authenticated using (true); create policy "update" on documents for update to authenticated using (owner_id = auth.uid()); -- mangler WITH CHECK: eier kan sette owner_id til en annen
create policy "owner read" on documents for select to authenticated using (owner_id = auth.uid()); create policy "owner update" on documents for update to authenticated using (owner_id = auth.uid()) with check (owner_id = auth.uid()); -- Tenant: medlemskap sjekkes i egen tabell create policy "org members read" on org_docs for select to authenticated using (public.is_org_member(auth.uid(), org_id));
Test RLS direkte fra browseren
Logg inn som User B og kjør i Console (klienten er allerede lastet i appen eller kan opprettes med publishable key):
// Forventet: [] eller bare egne rader
const { data } = await supabase.from("documents").select("*");
console.table(data);
// Forventet: 0 rader endret / feil
await supabase.from("documents").update({ title: "x" }).eq("id", "<A sin id>");
// Anonym test: uten innlogging
await supabase.auth.signOut();
await supabase.from("profiles").select("email");Rekursjon og roller
Policies som spør samme tabell (f.eks. profiles.role) gir rekursjon og ofte en «midlertidig» using(true). Bruk en SECURITY DEFINER-funksjon som has_role() mot en egen user_roles-tabell.
Quiz
Tabellen messages har RLS aktivert og én policy: for select using (sender_id = auth.uid() or recipient_id = auth.uid()). Kan en bruker sette inn meldinger som en annen avsender?
En server function bruker supabaseAdmin for å hente brukerens ordre fordi «RLS ga tomt resultat». Hva er riktig respons?