Audit RLS Supabase
Cette compétence exécute la partie base de données du scanner vibe-preflight (scripts/preflight.mjs à côté de ce fichier,
Node 20+, aucune dépendance, aucun réseau). Elle lit les fichiers .sql du projet (migrations Supabase,
schema.sql), rejoue les tables, politiques, vues, fonctions, octrois et buckets de stockage dans l'ordre, et signale
9 problèmes. Elle ne se connecte pas à une base de données.
Exécution
Depuis la racine du projet :
node ${CLAUDE_SKILL_DIR}/scripts/preflight.mjs . --only database --format json
Claude Code remplit ${CLAUDE_SKILL_DIR}. Autres agents : utilisez le dossier de cette compétence, par exemple
.agents/skills/supabase-rls-audit ou .claude/skills/supabase-rls-audit.
Le code de sortie 1 signifie au moins une erreur. Ajoutez --fail-on warning pour échouer aussi sur les avertissements, --explain (sortie
texte) pour expliquer pourquoi et comment corriger chaque problème. JSON : { summary: { error, warning }, findings: [{ rule, severity, message, file, line }] }.
Si le résultat indique 0 fichiers SQL, les politiques vivent probablement uniquement dans le tableau de bord. Demandez à l'utilisateur d'exécuter
supabase db pull (cela écrit le schéma distant dans une migration) et de relancer l'analyse. Ne rapportez pas le projet comme
propre à partir d'une analyse vide.
Les vérifications
| Règle | Niveau | Signification |
|---|---|---|
rls-disabled | erreur | Une table public n'a jamais enable row level security, donc n'importe qui avec la clé anonyme peut la lire et l'écrire via l'API. |
rls-permissive-policy | erreur en écriture, avertissement en lecture | using (true) ou with check (true). En insertion/mise à jour/suppression, tout utilisateur peut modifier les lignes d'autres utilisateurs. En lecture, chaque ligne est publique. |
rls-no-policy | avertissement | RLS activée mais aucune politique : l'API refuse tout. Parfait pour les tables réservées au serveur ; une liste vide silencieuse si le navigateur la lit. |
rls-update-privileged-column | avertissement | Les utilisateurs peuvent mettre à jour leur propre ligne et celle-ci possède une colonne plan, role, is_admin, credits ou similaire. La RLS limite les lignes, pas les colonnes. |
view-bypasses-rls | erreur | Une vue sans security_invoker = true s'exécute en tant que son propriétaire et contourne la RLS sur les tables sous-jacentes. |
auth-users-exposed | erreur | Une vue dans public sélectionne depuis auth.users. |
definer-search-path | avertissement | Une fonction security definer sans set search_path. |
definer-function-anon | erreur / avertissement | Une fonction security definer dans public que l'utilisateur anonyme peut appeler via /rest/v1/rpc/<nom>. Erreur avec un octroi explicite à l'anonyme, avertissement pour l'octroi par défaut de Supabase. |
storage-public-bucket | erreur / avertissement | Un bucket public. Erreur lorsqu'une politique de sélection sur storage.objects permet également à quiconque de lister le bucket. |
Comment corriger
Écrivez toujours une nouvelle migration. Ne modifiez jamais une migration déjà appliquée sur une base de données.
Politiques manquantes ou trop permissives : demandez quel accès l'application nécessite, puis limitez chaque politique à un rôle et au propriétaire.
alter table public.notes enable row level security;
create policy "notes : lire les siennes" on public.notes
for select to authenticated using ((select auth.uid()) = user_id);
create policy "notes : insérer les siennes" on public.notes
for insert to authenticated with check ((select auth.uid()) = user_id);
Colonnes privilégiées : autorisez les mises à jour uniquement sur les colonnes que les utilisateurs peuvent modifier.
revoke update on public.profiles from authenticated;
grant update (display_name, avatar_url) on public.profiles to authenticated;
Vues : alter view public.<nom> set (security_invoker = true); (Postgres 15+, tous les projets Supabase actuels).
Une vue matérialisée ne peut pas utiliser la RLS : déplacez-la hors de public ou révoquez la sélection de anon, authenticated. Pour auth.users, copiez les champs partageables dans public.profiles avec un déclencheur d'inscription.
Fonctions SECURITY DEFINER : ajoutez set search_path = '' et écrivez chaque nom avec son schéma. Ensuite, restreignez
qui peut l'appeler :
revoke execute on function public.admin_set_plan(uuid, text) from public, anon, authenticated;
grant execute on function public.admin_set_plan(uuid, text) to service_role;
Révoquer depuis public seul ne suffit pas sur Supabase, car l'utilisateur anonyme possède son propre octroi sur les nouvelles fonctions.
Stockage : rendez les buckets privés privés, limitez les lectures au dossier du propriétaire
((storage.foldername(name))[1] = (select auth.uid())::text) et servez des URL signées. Les images de produits et logos
peuvent rester publics.
Un résultat intentionnel (un catalogue public, une table réservée au serveur) peut être ignoré avec
-- vibe-preflight-ignore <id-règle> sur la ligne ou la ligne au-dessus. Indiquez la raison dans le même commentaire.
Signalement
- Ouvrez chaque ligne signalée et confirmez-la par rapport au SQL avant toute modification. Le scanner lit le texte SQL et peut se tromper ; indiquez-le quand c'est le cas.
- Regroupez le résumé par règle. Les erreurs d'abord, puis les avertissements que vous avez confirmés avec l'utilisateur.
- Relancez l'analyse après la correction et rapportez le nouveau résultat.
Limites
- Uniquement ce qui se trouve dans les fichiers
.sql. Les modifications effectuées uniquement dans le tableau de bord, les autres schémas et ce qu'une fonction fait à l'intérieur de son corps ne sont pas vérifiés. - Un résultat propre signifie que ces 9 motifs n'ont pas été trouvés. Cela ne prouve pas que les politiques correspondent aux règles de l'application. Tester en tant qu'utilisateur réel anonyme et connecté (le client Supabase avec la clé anonyme) reste utile.
- Pour le côté navigateur et environnement (clé service_role dans le code client, secrets dans les variables
NEXT_PUBLIC_, fichier.envcommité), exécutez l'analyse complète sans--only, ou utilisez la compétencevibe-preflight.