En bref
On active la RLS dans Supabase avec une instruction par table, alter table public.todos enable row level security;, ou avec l’option “Enable RLS” du dashboard. Les tables créées dans l’éditeur SQL ou par une migration ne l’ont pas par défaut. Juste après l’activation, la table devient silencieuse plutôt que bruyante. Lors de mon test local, la clé publique et deux utilisateurs connectés ont obtenu 200 [] sur select, update et delete, et seul insert a échoué avec 42501. La clé secrète voyait toujours les 3 lignes. Une vue simple sur la table donnait encore les 3 lignes à la clé publique. Depuis le 30 octobre 2026, une nouvelle table a aussi besoin d’un grant avant que quoi que ce soit l’atteigne : activez donc la RLS et écrivez les politiques dans la même migration que le grant.
Je propose un vérificateur de sécurité Supabase gratuit, et « RLS disabled » est le constat que je vois le moins bien expliqué. La plupart des guides sur la façon d’activer la RLS dans Supabase disent qu’ensuite « la table devient inaccessible ». C’est vrai pour les lectures et trompeur pour les écritures. J’ai donc construit une table sur une pile Supabase locale et, après chacune des étapes ci-dessous, je lui ai envoyé chaque type de requête : avec la clé publique, avec deux utilisateurs connectés et avec la clé secrète.
Qu’est-ce que la RLS dans Supabase ?
La RLS (row-level security) est une fonctionnalité de Postgres qui filtre les lignes par requête. Supabase transforme chaque requête de supabase-js en une requête de base de données exécutée sous un rôle : anon pour la clé publique (anon, publishable), authenticated pour un utilisateur connecté. Avec la RLS activée, Postgres vérifie vos politiques pour chaque ligne et ne renvoie, ne modifie ou ne supprime que les lignes qu’une politique autorise. Sans RLS, tout rôle qui a un grant sur la table atteint chaque ligne.
La clé secrète (service role) est différente. Son rôle a BYPASSRLS, la RLS ne s’applique donc jamais à elle. C’est pourquoi elle ne doit se trouver que sur un serveur. La différence entre les deux clés est expliquée dans Supabase anon key vs service role key : ce que chacune ouvre.
Comment activer la RLS dans Supabase ?
De trois façons, selon la manière dont la table a été créée :
- Quand vous créez une table dans le Table Editor, laissez cochée l’option “Enable Row Level Security (RLS)”. Dans le code source du dashboard, elle est marquée “Recommended”, et la décocher affiche un avertissement : la table sera lisible et modifiable publiquement.
- Pour une table existante, le guide de sécurité de l’API de Supabase dit : allez dans Database, puis Policies, et choisissez “Enable RLS” sur la table.
- En SQL, dans l’éditeur SQL ou une migration :
alter table public.todos enable row level security;
Le guide de Supabase est explicite : les tables créées dans le dashboard ont la RLS activée par défaut, celles créées dans l’éditeur SQL ou avec un autre outil non. Si vous construisez votre schéma avec des migrations ou un outil d’IA, vérifiez chaque table.
Que se passe-t-il juste après l’activation de la RLS ?
Rien n’échoue, sauf insert. J’ai créé public.todos avec une colonne de propriétaire (user_id uuid default auth.uid()), je l’ai accordée aux rôles de l’API comme dans les projets plus anciens, et j’ai ajouté trois lignes : deux appartenant à l’utilisateur A et une à l’utilisateur B. Puis j’ai activé la RLS sans aucune politique :
| Requête | Clé publique | Utilisateur A | Utilisateur B | Clé secrète |
|---|---|---|---|---|
| Select | 200, 0 ligne | 200, 0 ligne | 200, 0 ligne | 200, 3 lignes |
| Insert de sa propre ligne | 401, erreur 42501 | 403, erreur 42501 | 403, erreur 42501 | 201, ligne insérée |
| Update d’une ligne | 200, 0 ligne | 200, 0 ligne | 200, 0 ligne | 200, 1 ligne |
| Delete d’une ligne | 200, 0 ligne | 200, 0 ligne | 200, 0 ligne | 200, 1 ligne |
L’erreur d’insert est new row violates row-level security policy. Update et delete répondent 200 avec un tableau vide : une application qui ne vérifie pas les lignes renvoyées affiche « enregistré » alors que rien ne l’a été. Relisez vos écritures après avoir activé la RLS, ou demandez un décompte avec Prefer: count=exact.
Avant la RLS, avec les mêmes grants, la clé publique pouvait lire les 3 lignes, insérer, et modifier ou supprimer n’importe quelle ligne. L’utilisateur A pouvait modifier et supprimer la ligne de l’utilisateur B et lui donner ses propres lignes.
Comment rendre la table de nouveau utilisable ?
Écrivez une politique par opération. L’ensemble habituel pour ses propres lignes :
create policy "own select" on public.todos for select to authenticated
using ((select auth.uid()) = user_id);
create policy "own insert" on public.todos for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "own update" on public.todos for update to authenticated
using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "own delete" on public.todos for delete to authenticated
using ((select auth.uid()) = user_id);
Avec elles, l’utilisateur A voyait ses 2 lignes et l’utilisateur B sa seule ligne. La clé publique en voyait toujours 0. Quand A a essayé d’insérer une ligne appartenant à B, ou de déplacer sa propre ligne vers B, la réponse était 403, 42501. Ce refus vient de with check sur l’update. Quand A a essayé de modifier ou de supprimer la ligne de B, la réponse était 200, 0 rows. D’autres modèles (profils publics, équipes, MFA) sont dans Supabase RLS policy : exemples testés via l’API.
Un détail côté clé secrète : une ligne qu’elle a insérée a reçu user_id = null, parce qu’aucun utilisateur ne se trouve derrière cette clé pour auth.uid(). La RLS a été ignorée, mais la valeur par défaut n’a pas fait ce que le nom de la colonne laisse croire.
Que lisait la clé publique à chaque étape ?
Deux étapes laissent tout fuir : un grant sans RLS, et une vue simple sur une table dont les politiques sont correctes. Les deux sections suivantes expliquent les deux cas.
A-t-on encore besoin de la RLS après le changement de grants du 30 octobre ?
Oui. Depuis le 30 octobre 2026, Supabase n’accorde plus automatiquement aux rôles de l’API les nouvelles tables de public, dans les projets neufs comme existants. Ma pile locale se comporte déjà ainsi. Une table fraîchement créée sans RLS a répondu 42501 à tous les rôles, clé secrète comprise : rien n’a fui. Les détails sont dans Supabase, changement des grants du 30 octobre : que vérifier.
Mais le grant revient dès que l’application a besoin de la table, et à partir de là la RLS est la seule chose entre la clé publique et chaque ligne. Mettez donc tout dans une seule migration, dans cet ordre :
create table public.todos (...);
alter table public.todos enable row level security;
create policy "own select" on public.todos ...;
grant select, insert, update, delete on table public.todos to authenticated;
N’accordez qu’aux rôles qui ont besoin de la table. Quand j’ai tout révoqué à anon, le select de la clé publique est passé de 200 [] à 401, 42501. Le grant est le premier verrou, la RLS le second.
Pourquoi une vue laisse-t-elle quand même fuir des lignes ?
Parce qu’une vue s’exécute avec les droits de son propriétaire, et dans Supabase c’est postgres, qui contourne la RLS. Avec les politiques de lignes personnelles en place sur todos, un simple create view public.todos_view as select * from public.todos a renvoyé les 3 lignes à la clé publique et à l’utilisateur B.
Recréée avec with (security_invoker = true), la même vue a renvoyé 0 ligne à la clé publique, 2 à A et 1 à B, exactement comme la table :
create view public.todos_view with (security_invoker = true) as
select * from public.todos;
L’Advisor de Supabase signale la version simple sous le nom security_definer_view ; ce que cela signifie et comment le corriger est dans Supabase security definer view : pourquoi elle contourne RLS.
Comment activer la RLS automatiquement sur chaque nouvelle table ?
Avec un event trigger. Le guide des event triggers de Supabase fournit une fonction rls_auto_enable() et un trigger ensure_rls qui s’exécute après create table, create table as et select into dans public. Je l’ai installé tel que publié et j’ai obtenu :
- Une table créée ensuite avec
create tableavait la RLS activée sans aucunalter. La clé publique a obtenu200 []sur select et401sur insert. - Une table créée avec
create table as selectavait aussi la RLS activée. - Une table créée avant le trigger est restée sans RLS. Le trigger n’est pas rétroactif.
Sans le trigger, une seconde table créée à côté de la table protégée n’avait pas la RLS, et une fois accordée, la clé publique la lisait et y écrivait librement. Le trigger ne couvre aussi que public ; les autres schémas exposés demandent leur propre ligne dans la fonction.
FORCE ROW LEVEL SECURITY aide-t-il ?
Pas pour les rôles qui vous inquiètent. alter table ... force row level security fait s’appliquer la RLS au propriétaire de la table aussi. Dans Supabase, postgres comme service_role ont BYPASSRLS : après force, le propriétaire comptait donc toujours 3 lignes, la clé secrète voyait toujours tout, et une vue appartenant à postgres fuyait encore. Cela n’a changé quelque chose que pour un rôle propriétaire créé sans BYPASSRLS : 2 lignes avant, 0 après.
Comment vérifier son propre projet ?
- Dans l’éditeur SQL, listez les tables de
publicdont la RLS est désactivée :
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
and c.relkind in ('r', 'p')
and not c.relrowsecurity
order by 1;
- L’Advisor du dashboard signale la même chose sous le nom
rls_disabled_in_public; ce que cette erreur veut dire est dans Supabase « RLS disabled in public » : ce que cela signifie. - Le vérificateur de sécurité Supabase gratuit lit votre projet en lecture seule et liste les tables sans RLS que l’API peut atteindre, les vues qui contournent la RLS et les politiques qui autorisent tout le monde, avec le SQL de correction pour chacune.
- Un scanner ne peut pas savoir à qui appartiennent les lignes. Pour le prouver, envoyez des requêtes avec deux utilisateurs, comme ci-dessus. La méthode complète pour une application avec des rôles est dans Comment tester Supabase RLS : 30 droits sur 87 sans effet.
Comment j’ai testé
- Supabase CLI 2.109.1 sur un portable : Postgres 17.6, PostgREST 14.14, Auth 2.192.0. Deux utilisateurs connectés par mot de passe, plus les clés locales publishable et secrète.
- Une table,
public.todos, réinitialisée avec les mêmes 3 lignes avant chaque requête. Chaque étape a été appliquée en SQL, et chaque requête est passée par l’API REST, avec son code de statut et son corps enregistrés. - Les grants des étapes 2 à 5 ont été ajoutés à la main, pour correspondre à un projet créé avant le 30 octobre 2026.
Limites. Une seule pile locale, des données inventées, 28 octobre 2026. Les étapes du dashboard sont citées d’après la documentation de Supabase, pas parcourues en cliquant. Les tables créées par le rôle propre du dashboard peuvent recevoir d’autres grants par défaut. Je ne l’ai pas testé, ni Storage, Realtime ou GraphQL.
Pas sûr que vos tables soient verrouillées ?
Je regarde volontiers un schéma avec vous. Sans argumentaire commercial.
Me contacter