---
title: "Supabase security definer view: derfor omgår det RLS"
description: "Hvorfor et Supabase-view returnerer rader som RLS skjuler på tabellen, hva Advisor-varselet security_definer_view betyr, og innstillingen som fikser det."
url: "https://madsalt.tech/no/notes/supabase-security-definer-view/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/no/about/"
language: "no"
published: 2026-11-02
---

# Supabase security definer view: derfor omgår det RLS

TL;DR

Et Postgres-view kjører med rettighetene til eieren sin, med mindre det er opprettet med `security_invoker = true`. I Supabase er eieren vanligvis `postgres`, som omgår RLS, så et view over en beskyttet tabell returnerer rader som selve tabellen skjuler. På den lokale stacken min fikk den offentlige nøkkelen 0 rader fra tabellen og alle 3 av 3 fra et vanlig view over den; med `security_invoker = true` returnerte viewet også 0. På Postgres 15 og nyere: sett det på hvert view i et eksponert skjema.

Den gratis [Supabase-sikkerhetssjekken](https://madsalt.tech/no/tools/supabase-security-check/) min har en regel som heter "Views som omgår RLS", og Supabases Security Advisor melder det samme som `security_definer_view`. Det er lett å overse, fordi tabellen det lekker fra ser beskyttet ut.

## Hvorfor omgår et Supabase-view RLS?

Et view er en lagret spørring. Som standard sjekker Postgres rettigheter og RLS på tabellene inni det som viewets eier, ikke som den som kaller det. Supabases [RLS-guide](https://supabase.com/docs/guides/database/postgres/row-level-security) sier det rett ut: views omgår RLS som standard fordi de vanligvis opprettes med brukeren `postgres`, og et view over en beskyttet tabell deler ut hver rad som policyene skulle holde tilbake.

Data API-et serverer views som tabeller. Hvis viewet ligger i et eksponert skjema og `anon` eller `authenticated` kan gjøre `select` på det, leser den offentlige nøkkelen det.

## Hva returnerte viewet i testen min?

På en lokal Supabase-stack (CLI 2.109.1, Postgres 17.6, PostgREST 14.14) opprettet jeg en tabell `orders` med tre falske kunder, slo på RLS uten policy, og la et view `order_summary` over den med `select`-tilgang for `anon`. Så kalte jeg begge med publishable key:

![Samme tabell, tre måter å lese den på. Lokal Supabase, RLS på uten policy, publishable key. Selve tabellen: Publishable key → orders, RLS på → 0 rader; Et vanlig view over den: Publishable key → order_summary-view → 3 av 3 rader; Viewet med security_invoker: Publishable key → security_invoker-view → 0 rader.](https://madsalt.tech/charts/no/supabase-security-definer-view.svg)

*Reproduksjonen min på en lokal Supabase-stack, 23. oktober 2026.*

| Forespørsel med publishable key                              | Resultat                           |
| ------------------------------------------------------------ | ---------------------------------- |
| `GET /rest/v1/orders` (RLS på, ingen policy)                 | `[]`                               |
| `GET /rest/v1/order_summary` (vanlig view)                   | alle 3 rader, med navn og e-poster |
| `GET /rest/v1/order_summary` etter `security_invoker = true` | `[]`                               |

Samme tabell, samme nøkkel. Bare innstillingen på viewet endret hva som kom tilbake.

## Hvordan fikser du det?

På Postgres 15 og nyere, som alle dagens Supabase-prosjekter kjører, lar du viewet sjekke RLS som den som kaller:

```sql
alter view public.order_summary set (security_invoker = true);
```

For et nytt view legger du det i definisjonen:

```sql
create view public.order_summary
with (security_invoker = true)
as select customer, email, total from public.orders;
```

Den som kaller trenger da også `select` på den underliggende tabellen, og policyene på tabellen avgjør radene. Hvis et view må aggregere data som den som kaller ikke kan se rad for rad, for eksempel et offentlig antall, hold det utenfor det eksponerte skjemaet og server det i stedet gjennom en funksjon med et smalt resultat.

Slik lister du views i `public` som fortsatt kjører som eieren sin:

```sql
select c.relname as view
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'v'
  and not coalesce(
    (select bool_or(option = 'security_invoker=true' or option = 'security_invoker=on')
     from unnest(c.reloptions) as option), false);
```

På stacken min, med ett vanlig view og to med `security_invoker = true`, returnerte den bare det vanlige.

Materialiserte views støtter ikke RLS i det hele tatt, så sjekken flagger dem i API-et separat, under "Materialiserte views eller eksterne tabeller i API-et". Hva annet som går utenom policyene, fra secret key til `SECURITY DEFINER`-funksjoner, står i [sikkerhetsjekklisten min for Lovable-apper](https://madsalt.tech/no/blog/lovable-app-security-checklist/), og hvordan du beviser selve policyene, står i [hvordan du tester Supabase RLS](https://madsalt.tech/no/blog/how-to-test-supabase-rls/). En policy som slår opp team gjennom en hjelpefunksjon, uten rekursjon, står i [Supabase RLS policy-eksempler, testet via API-et](https://madsalt.tech/no/blog/supabase-rls-policy-examples/). En security definer-funksjon trenger én innstilling til, en fast `search_path`; hvorfor `search_path = public` ikke holdt i testen min, står i [Supabase Function Search Path Mutable: hva det betyr](https://madsalt.tech/no/notes/supabase-function-search-path-mutable/).
