Aller au contenu

Comment tester Supabase RLS : 30 droits sur 87 sans effet

Voir en MarkdownOuvrir dans ClaudeOuvrir dans ChatGPTOuvrir dans Perplexity

La question est aussi copiée. Si le chat reste vide, collez-la.

En bref

Testez la row level security (sécurité au niveau des lignes) de Supabase comme un membre la rencontre : connectez-vous avec chaque rôle, lancez l’action réelle avec une permission désactivée puis activée, et vérifiez que le résultat change. Sur une application en production avec cinq rôles modifiables, cette méthode a montré que 30 des 87 cases de permission qu’un administrateur pouvait enregistrer ne changeaient rien dans la base de données. Un test politique par politique ne peut pas le voir, car il teste la politique que vous avez écrite, pas la case à l’écran.

Je suis Denys, ingénieur full-stack et IA. Plus tôt cette année, j’ai construit sur Supabase un outil de suivi des projets et du temps pour un cabinet d’audit, où chaque règle d’accès vit dans la base de données. Voici la méthode que j’utilise pour prouver ces règles, ce qu’elle a trouvé et ce qu’elle coûte à faire tourner.

Pourquoi les tests de politiques ne suffisent-ils pas pour tester Supabase RLS ?

Un test de politique répond à la question « cette politique fait-elle ce que j’ai écrit ? ». Vos utilisateurs posent une autre question : « si je décoche cette case pour ce rôle, peuvent-ils encore le faire ? ». Sur une petite application, les deux questions ont la même réponse. Sur une application dont la page de paramètres permet aux administrateurs de modifier une matrice de permissions, elles divergent.

L’application comptait 41 tables avec RLS activée et 402 instructions CREATE POLICY réparties sur 255 migrations. L’écran d’administration affichait une matrice des domaines (projets, tâches, feuilles de temps, fichiers, etc.) croisés avec les actions (voir, créer, modifier, archiver), une colonne par rôle. Chaque case enregistrée était une promesse faite à l’administrateur. Rien ne vérifiait que la base de données la tenait.

Supabase documente bien les outils standard : les tests pgTAP et les helpers de test permettent de créer des utilisateurs, de basculer vers eux et de vérifier ce qu’ils peuvent lire. Utilisez-les. La méthode ci-dessous vient par-dessus : elle part de la matrice que voit l’administrateur, pas de la liste des politiques.

Comment fonctionne la preuve par la matrice ?

Chaque case de la matrice devient une sonde. Une sonde se connecte en tant que membre du rôle, effectue une action réelle et enregistre le résultat. Elle le fait deux fois : une fois case désactivée, une fois case activée, tout le reste identique. Si le résultat change, la case est appliquée. Sinon, la case ne fait rien, aussi convaincante qu’elle paraisse à l’écran.

  1. Une case de la matriceExemple : Standard Member · Tâches · Créer
  2. Case désactivée : agir en tant que membre du rôleCréer une tâche par le même chemin que l'application. Enregistrer le résultat.
  3. Case activée : répéter la même actionMême utilisateur, mêmes données. Seule la case a changé.
  4. Comparer les deux résultatsLe verdict dépend uniquement de ce qu'a fait la base de données.
  • Le résultat a changéAppliquée. Refusé quand elle est désactivée, autorisé quand elle est activée.
  • Identique dans les deux casSans effet. L'écran promet un contrôle qui n'existe pas.
  • L'écran et la base de données divergentUn bug. La page affiche un bouton que la base refuse, ou l'inverse.
Figure 1. Chaque verdict vient d'une paire de résultats réels, jamais de la lecture du code.

Ici, « résultat » désigne ce que fait un consommateur de la permission : une politique renvoie des lignes ou refuse une écriture, une fonction de la base lève une erreur, un trigger bloque, ou une edge function répond 403. Une même case peut protéger plusieurs de ces points, si bien que les 87 cases ont demandé 279 vérifications par rôle. Chaque vérification s’est terminée par l’un de ces verdicts :

Verdict Vérifications par rôle Ce que cela signifie
Effet 227 Le résultat s’est inversé avec la case
Sans effet 30 Le résultat est resté le même, case désactivée ou activée
Inaccessible 19 L’action est hors de portée de ce rôle, prouvé séparément
Edge function 1 Appliqué par une edge function, vérifié via HTTP
Écran seulement 1 Ne change que ce qu’affiche la page, couvert par un test front-end
Écran et base concordent 1 Ce que propose la page correspond à ce que la base autorise
Échec 0 Une vérification dont le résultat ne correspondait pas à l’attendu

Qu’a trouvé la preuve ?

L’exécution du 15 septembre 2026 a couvert les cinq rôles qu’un administrateur peut modifier (Admin, Senior, Standard, Restricted et un rôle personnalisé cloné). Les résultats étaient identiques pour tous les rôles.

87 cases de permission qu'un administrateur pouvait enregistrer. Selon ce qui appliquait réellement chaque case. Pareil pour les cinq rôles. Appliquées par la base de données: 55; Appliquées ailleurs : 1 edge function, 1 écran: 2; Sans aucun effet: 30.
Figure 2. Les 87 cases de permission qu'un administrateur pouvait enregistrer, selon ce qui les appliquait réellement. Même résultat pour chacun des cinq rôles.

Aucune des 30 ne laissait entrer qui que ce soit. Pour 29 d’entre elles, aucune politique, fonction ou trigger ne lisait jamais la case ; la dernière avait cessé de compter après la réécriture d’une politique. Le problème reste réel : un administrateur qui décoche « Supprimer » pour un rôle croit que ce rôle ne peut plus supprimer. Nous avons masqué les 30 au lieu de les câbler, car chacune aurait demandé une décision produit, et une case qui ne fait rien n’a pas sa place à l’écran pendant qu’on la prend.

Le même travail a fait apparaître quatre autres défauts :

Ce qui n’allait pas Comment cela s’est vu Correction
30 cases ne faisaient rien Verdict « sans effet », résultat identique case désactivée ou activée Masquées dans la matrice
Trois fonctions de la base laissaient passer une action quand une tâche n’avait pas d’assigné Une sonde qui aurait dû être refusée a été autorisée Les fonctions refusent désormais quand la valeur manque
Remettre une feuille de temps en brouillon dépendait de la mauvaise case L’écran et la base divergeaient pour un rôle Rattaché à la permission de réouverture, sa vraie place
Un membre ne voyait les nouvelles permissions qu’après un rechargement La base avait changé, la page non Les permissions se rafraîchissent toutes les 60 secondes et au retour sur la fenêtre
La valeur par défaut d’une colonne violait sa propre contrainte de vérification, donc les insertions sans cette colonne échouaient Trouvé par la revue de sécurité de la correction, pendant le même travail Valeur par défaut remplacée par une valeur que la contrainte accepte

Le bug de l’assigné manquant était celui qui comptait. Les garde-fous étaient écrits ainsi en PL/pgSQL :

if not (has_permission or assignee_id = auth.uid()) then
  raise exception 'forbidden';
end if;

Quand assignee_id est null, assignee_id = auth.uid() vaut null, false or null vaut null, not null vaut toujours null, et if traite null comme faux. L’exception ne se déclenche jamais. En production, aucune tâche n’avait d’assigné, donc ce n’était pas un cas limite : cela touchait toutes les tâches concernées. La correction enveloppe la condition dans coalesce(…, false) pour qu’une valeur manquante soit refusée, et elle est partie en premier, seule, avant le reste du lot.

Comment la lancer sur une copie de la production ?

Une preuve sur un schéma différent de la production ne prouve rien sur la production. Le harnais tourne sur la stack Supabase locale, dans une base séparée qui copie le schéma de production plus les migrations en revue. Avant chaque exécution, il compare une empreinte des deux bases locales : 799 lignes qui décrivent tables, politiques, fonctions et droits. La moindre différence arrête l’exécution.

  1. Schéma de productionCopié sur une seule machine de confiance. Il ne part jamais sur un runner CI hébergé.
  2. Stack locale : base de l'applicationServie par l'API et l'auth locales, utilisée par les vérifications de bout en bout.
  3. Stack locale : base de preuve isoléeMême schéma, utilisée uniquement par la preuve par la matrice.
  4. Contrôle de l'empreinte799 lignes par base. La moindre différence arrête l'exécution avant la première sonde.
  5. Les sondes tournent avec chaque rôleEn vrais membres connectés, par les mêmes chemins que l'application.
  6. Aucune trace laisséeLe nombre de lignes à la fin doit égaler celui du départ.
Figure 3. La preuve ne compte que si la base sur laquelle elle a tourné correspond à la production.

Deux détails ont rendu le harnais fiable :

  • Le casser exprès d’abord. Avant de faire confiance à une exécution verte, j’ai remplacé une politique d’insertion par une politique qui accepte tout, puis relancé la preuve. Elle devait passer au rouge sur les cases que cette politique protège. Un harnais qui reste vert avec une politique cassée n’est que de la décoration.
  • Vérifier aussi l’état enregistré. Le premier passage part d’une base propre pour chaque rôle. Un second passage part de la matrice enregistrée de chaque rôle, car une case peut se comporter autrement à côté des cases qu’un administrateur a réellement cochées.

Comment la garder verte après la mise en production ?

Une preuve lancée une seule fois est périmée dès la migration suivante. Elle tourne désormais avant chaque push qui touche les migrations, le code d’authentification ou la matrice elle-même.

  1. git pushLe changement touche les migrations, l'auth ou la matrice.
  2. Hook pre-push : preuve par la matriceTourne en local sur la base de preuve. Rouge bloque le push.
  3. Le push arrive sur mainLe déploiement se fait comme d'habitude.
  4. Exécution CI auto-hébergéeLa même preuve à nouveau, sur la machine qui détient la copie du schéma de production.
Figure 4. Le contrôle bloquant est local, parce que la copie du schéma de production reste sur une seule machine.

La preuve compte 135 tests dans 11 fichiers, à côté d’environ 3 100 tests unitaires ordinaires dans le même projet.

Qu’est-ce qui n’a pas marché ?

  • La CI hébergée ne pouvait pas la lancer. La preuve a besoin de la copie du schéma de production, et je ne voulais pas la mettre sur un runner hébergé. Le contrôle bloquant est donc un hook pre-push, et la CI sur une machine auto-hébergée est une seconde vérification que le déploiement n’attend pas.
  • Les nombres de lignes fixes ont cassé. La première vérification « aucune trace » comparait à des nombres fixes. La migration légitime suivante a ajouté une ligne de permission et l’a fait passer au rouge. Elle compare maintenant les nombres de la fin à ceux du départ.
  • Ignoré n’est pas réussi. Les vérifications de bout en bout ont besoin du runtime edge local. Quand il est arrêté, elles sont ignorées, et un test ignoré se lit facilement comme vert.
  • Elle est lente sur un portable chargé. Les passages par rôle ont demandé des timeouts plus longs sur une machine occupée.
  • Elle prouve ce que décrivent les sondes. Une case pour laquelle personne n’a écrit de sonde est signalée comme un trou de couverture, pas comme sûre.

Comment reproduire cela sur votre application Supabase

  1. Listez chaque permission que l’écran permet d’enregistrer : rôle × domaine × action. Cette liste est la spécification, pas vos politiques.
  2. Pour chaque case, notez l’action réelle qu’elle doit contrôler et la forme que prend un refus (aucune ligne, une erreur, un 403).
  3. Montez une base qui correspond à la production et prenez son empreinte, pour qu’une dérive du schéma arrête l’exécution.
  4. Lancez chaque action en tant que membre connecté, case désactivée puis activée, et comparez.
  5. Cassez exprès une politique et vérifiez que la preuve passe au rouge.
  6. Masquez ou câblez chaque case sans effet, puis faites de la preuve un contrôle avant le push.

Si votre application a déjà la RLS mais que vous n’êtes pas sûr qu’elle fasse ce que dit l’écran des paramètres, la même méthode sert d’audit d’une base de code existante, la première étape d’un sauvetage de projet. J’ai décrit le projet derrière ces chiffres dans le cas du suivi du temps pour un cabinet d’audit.

Pas sûr que votre RLS corresponde à votre écran de paramètres ?

Je compare volontiers nos façons de la tester. Sans argumentaire commercial.

Me contacter