---
title: "Supabase security definer view : pourquoi elle contourne RLS"
description: "Pourquoi une vue Supabase renvoie des lignes que la RLS masque sur sa table, ce que signifie l'alerte security_definer_view et le réglage qui la corrige."
url: "https://madsalt.tech/fr/notes/supabase-security-definer-view/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/fr/about/"
language: "fr"
published: 2026-11-02
---

# Supabase security definer view : pourquoi elle contourne RLS

En bref

Une vue Postgres s'exécute avec les droits de son propriétaire, sauf si elle est créée avec `security_invoker = true`. Dans Supabase, le propriétaire est en général `postgres`, qui contourne la RLS : une vue sur une table protégée renvoie donc des lignes que la table elle-même cache. Sur mon stack local, la clé publique a obtenu 0 ligne de la table et 3 sur 3 d'une vue simple par-dessus ; avec `security_invoker = true`, la vue en a renvoyé 0 aussi. Dès Postgres 15, activez-le sur chaque vue d'un schéma exposé.

Mon [audit de sécurité Supabase](https://madsalt.tech/fr/tools/supabase-security-check/) gratuit a une règle appelée « Vues qui contournent RLS », et le Security Advisor de Supabase signale la même chose sous le nom `security_definer_view`. On la manque facilement, parce que la table d'où ça fuit a l'air protégée.

## Pourquoi une vue Supabase contourne-t-elle la RLS ?

Une vue est une requête enregistrée. Par défaut, Postgres vérifie les droits et la RLS des tables qu'elle contient en tant que propriétaire de la vue, pas en tant que la personne qui l'appelle. Le [guide RLS](https://supabase.com/docs/guides/database/postgres/row-level-security) de Supabase le dit sans détour : les vues contournent la RLS par défaut, parce qu'elles sont en général créées avec l'utilisateur `postgres`, et une vue sur une table protégée distribue toutes les lignes que ses politiques devaient retenir.

La Data API sert les vues comme des tables. Si la vue est dans un schéma exposé et que `anon` ou `authenticated` peut la lire, la clé publique la lit.

## Que renvoyait la vue dans mon test ?

Sur un stack Supabase local (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), j'ai créé une table `orders` avec trois faux clients, activé la RLS sans politique, et une vue `order_summary` par-dessus avec un droit `select` pour `anon`. Puis j'ai appelé les deux avec la publishable key :

![Même table, trois façons de la lire. Supabase local, RLS activée sans politique, publishable key. La table elle-même: Publishable key → orders, RLS activée → 0 ligne; Une vue simple par-dessus: Publishable key → vue order_summary → 3 lignes sur 3; La vue avec security_invoker: Publishable key → vue security_invoker → 0 ligne.](https://madsalt.tech/charts/fr/supabase-security-definer-view.svg)

*Ma reproduction sur un stack Supabase local, 23 octobre 2026.*

| Requête avec la publishable key                              | Résultat                           |
| ------------------------------------------------------------ | ---------------------------------- |
| `GET /rest/v1/orders` (RLS activée, aucune politique)        | `[]`                               |
| `GET /rest/v1/order_summary` (vue simple)                    | les 3 lignes, avec noms et e-mails |
| `GET /rest/v1/order_summary` après `security_invoker = true` | `[]`                               |

Même table, même clé. Seul le réglage de la vue a changé ce qui revenait.

## Comment la corriger ?

Dès Postgres 15, que fait tourner tout projet Supabase actuel, faites vérifier la RLS à la vue en tant qu'appelant :

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

Pour une nouvelle vue, mettez-le dans la définition :

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

L'appelant doit alors aussi avoir `select` sur la table sous-jacente, et ce sont les politiques de la table qui décident des lignes. Si une vue doit agréger des données que l'appelant ne peut pas voir ligne par ligne, comme un compteur public, gardez-la hors du schéma exposé et servez-la plutôt par une fonction au résultat étroit.

Pour lister les vues de `public` qui s'exécutent encore en tant que leur propriétaire :

```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);
```

Sur mon stack, avec une vue simple et deux avec `security_invoker = true`, elle n'a renvoyé que la vue simple.

Les vues matérialisées ne gèrent pas du tout la RLS ; l'audit les signale donc à part, sous « Vues matérialisées ou tables étrangères dans l’API ». Tout le reste de ce qui passe outre les politiques, de la secret key aux fonctions `SECURITY DEFINER`, est dans ma [checklist de sécurité d'une application Lovable](https://madsalt.tech/fr/blog/lovable-app-security-checklist/), et la façon de prouver les politiques elles-mêmes dans [comment tester Supabase RLS](https://madsalt.tech/fr/blog/how-to-test-supabase-rls/). Une politique qui cherche les équipes via une fonction d'aide, sans récursion, est dans [Supabase RLS policy : exemples testés via l’API](https://madsalt.tech/fr/blog/supabase-rls-policy-examples/). Une fonction security definer demande un réglage de plus, un `search_path` fixe ; pourquoi `search_path = public` ne suffisait pas dans mon test est expliqué dans [Supabase Function Search Path Mutable : ce que ça signifie](https://madsalt.tech/fr/notes/supabase-function-search-path-mutable/).
