← Guides par stack

Supabase

Sécurité pour ton app avec Supabase

Supabase expose ta base de données via une API dès la première minute. Sans un RLS bien réglé ou avec la mauvaise clé dans le frontend, n'importe qui peut lire tes tables. Vérifie les bases avant de lancer.

Risques typiques dans Supabase

Des tables sans RLS

Row Level Security est la barrière principale. Une nouvelle table sans RLS est ouverte en lecture et écriture depuis le navigateur avec la clé publique.

La service_role dans le frontend

La clé service_role contourne toute la sécurité. Si elle finit dans le JavaScript du client, elle donne le contrôle total de ta base de données.

Des buckets de Storage ouverts

Un bucket public sert n'importe quel fichier à celui qui a l'URL. Les documents privés doivent aller dans des buckets protégés par des policies.

« Sécurité Supabase » pour une appli construite avec l'IA, ce sont deux choses différentes. La première, c'est la configuration du projet : ce que Supabase expose par conception et ce qu'il te revient de fermer. La seconde, c'est ce que Lovable, Bolt ou Cursor font avec Supabase quand ils génèrent le code, et c'est de là que viennent presque tous les trous. Ce guide couvre les deux, avec trois requêtes SQL pour auditer ton projet en une minute, un prompt de revue pour ton IA et la façon de vérifier de l'extérieur que tout est fermé.

Partie 1 : comment fonctionne la sécurité dans Supabase

L'API de données est publique par conception

Supabase place devant ton Postgres une API REST (PostgREST) que le navigateur appelle directement avec la clé anon (ou la nouvelle clé publishable). Cette clé part dans le JavaScript de tous tes utilisateurs et c'est normal : elle identifie le projet, elle ne donne aucune permission. Ce qui décide qui peut lire ou écrire chaque ligne, c'est le Row Level Security. L'autre clé, service_role (ou secret), contourne entièrement le RLS et ne peut vivre que sur un serveur. Si tu comprends cette séparation, tu comprends 80 % de la sécurité Supabase.

RLS et policies : la seule vraie barrière

Une nouvelle table dans le schéma public est accessible par l'API dès que tu la crées. Sans RLS activé, n'importe qui avec la clé anon lit et écrit toutes ses lignes. Avec RLS activé et sans policies, personne n'y accède. Avec des policies, ceux qu'elles désignent. D'où la règle : RLS sur toutes les tables et une policy explicite par opération (select, insert, update, delete). Les patterns concrets (chaque utilisateur ses lignes, appartenance à une équipe, contenu public en lecture seule) sont dans ce qu'il faut vérifier dans Supabase avant de lancer.

Ce qui contourne le RLS sans que tu le remarques

  • Les vues. Par défaut, une vue s'exécute avec les permissions de celui qui l'a créée, c'est-à-dire qu'elle ignore le RLS des tables qu'elle interroge. Crée-les avec with (security_invoker = on).
  • Les fonctions security definer. Une fonction RPC marquée ainsi tourne comme son propriétaire et lit ce qu'elle veut. Utilise-les seulement quand c'est indispensable, avec set search_path = '', et retire l'exécution à anon si un visiteur ne doit pas l'appeler.
  • Les schémas exposés. Tout ce qui est dans public (tables, vues et fonctions) est atteignable par l'API. Si une fonction est interne, déplace-la dans un autre schéma.
  • La clé service_role. Dans le frontend, dans un dépôt public ou dans un prompt collé à une IA, c'est le contrôle total de la base de données.

Auth : des réglages à regarder

Dans Authentication, tu as la confirmation d'e-mail (laisse-la activée), la liste des Redirect URLs autorisées (si elle est vide ou avec des jokers, un lien magique peut atterrir sur un domaine étranger), les limites de requêtes, le captcha pour l'inscription et la récupération, la politique de mots de passe, la protection contre les mots de passe compromis et la connexion anonyme (désactivée sauf si tu l'utilises exprès). Chacun est une case à cocher ; aucun n'est mauvais par défaut, mais l'IA a tendance à les toucher pour que « les tests passent ».

Storage, Realtime et Edge Functions

Les buckets publics servent n'importe quel fichier à qui a l'URL : pour les envois des utilisateurs, utilise des buckets privés, des policies sur storage.objects et des URL signées avec expiration, plus une limite de taille et des types autorisés par bucket. En Realtime, les changements Postgres respectent le RLS, mais les canaux de broadcast et de présence ont besoin de leurs propres policies s'ils transportent des données privées. Dans les Edge Functions, les secrets passent par supabase secrets set, jamais dans le code, et si tu désactives la vérification JWT pour un webhook, valide toi-même la signature de l'expéditeur.

Le projet lui-même

La chaîne de connexion directe à Postgres (avec le mot de passe de la base) ne doit jamais quitter ton serveur ni tes variables d'environnement. Active l'obligation de SSL, envisage les restrictions réseau si tu as un plan payant, et consulte de temps en temps le Security Advisor du tableau de bord (Database, Advisors) : il signale les tables sans RLS, les vues security definer, les fonctions à search_path modifiable et les tables auth exposées.

Partie 2 : ce que l'IA fait mal avec Supabase

Voici les erreurs qui reviennent le plus dans les applis générées avec Lovable, Bolt ou Cursor, par ordre de fréquence :

  • Créer des tables sans RLS. Le générateur écrit la migration, l'appli fonctionne et personne n'active rien. C'est l'origine de la CVE-2025-48757 de Lovable, qui a exposé les données de centaines de projets.
  • Des policies using (true). Quand le RLS bloque quelque chose, l'IA « répare » avec une policy qui laisse tout passer. C'est du RLS désactivé sous un autre nom.
  • service_role dans le navigateur. Une requête échoue sur les permissions et la solution rapide, c'est de changer la clé. Cherche service_role dans ton bundle tout de suite.
  • Des fonctions security definer pour esquiver les erreurs. Même pattern : au lieu d'écrire la bonne policy, on emballe la requête dans une fonction qui tourne en superutilisateur.
  • Des buckets publics pour les fichiers des utilisateurs. Les avatars, d'accord ; les factures, documents ou exports, non.
  • Des champs de privilège modifiables par l'utilisateur. Un role ou is_admin dans la table profiles avec une policy d'update qui n'exclut pas ces colonnes. N'importe qui se fait admin depuis la console du navigateur.
  • Désactiver la confirmation d'e-mail ou les limites pour que les tests aillent plus vite, et l'oublier en production.

Trois requêtes SQL pour auditer ton projet

Colle-les dans l'éditeur SQL du tableau de bord. La première liste les tables publiques sans RLS ; l'idéal, c'est qu'elle ne renvoie rien :

select n.nspname as schema, c.relname as table
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r' and n.nspname = 'public' and not c.relrowsecurity;

La deuxième trouve les policies qui laissent tout passer :

select tablename, policyname, cmd, roles
from pg_policies
where schemaname = 'public' and (qual = 'true' or with_check = 'true');

Une telle policy sur une table de contenu public en lecture seule peut être correcte ; sur profiles, orders ou messages, non. La troisième liste les fonctions qui tournent avec les permissions de leur propriétaire :

select p.proname, pg_get_function_identity_arguments(p.oid) as args
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.prosecdef;

Chaque résultat devrait avoir une raison que tu peux expliquer. Sinon, retire le security definer ou déplace la fonction hors de public.

Un prompt de revue avant de déployer

Colle-le dans Lovable, Bolt ou Cursor avec accès à ton schéma et à ton code :

Passe en revue la sécurité de cette appli Supabase et ne change rien pour l'instant. Réponds avec une liste :
1. Tables du schéma public sans RLS activé.
2. Policies avec using (true) ou with check (true), et si elles sont justifiées.
3. Colonnes de privilège (role, is_admin, plan, credits) qu'un utilisateur pourrait modifier avec les policies d'update actuelles.
4. Vues sans security_invoker et fonctions security definer, avec la raison de chacune.
5. Tout usage de la clé service_role ou de la chaîne de connexion Postgres hors du serveur.
6. Buckets publics contenant des fichiers d'utilisateurs.
7. Réglages Auth désactivés (confirmation d'e-mail, limites, captcha) et redirect URLs avec jokers.
Pour chaque point, propose le SQL ou le changement exact, et attends ma confirmation.

Vérifie le résultat de l'extérieur

Le test définitif, ce n'est pas le tableau de bord, c'est ce que l'API répond à un inconnu. Avec l'URL de ton projet et la clé anon (les deux sont dans le code du navigateur, donc un attaquant les a aussi) :

curl -s "https://TON_PROJET.supabase.co/rest/v1/profiles?select=*" -H "apikey: TA_CLE_ANON" -H "Authorization: Bearer TA_CLE_ANON"

Si ça renvoie des lignes, cette table est ouverte. Si ça renvoie [] ou une erreur de permissions, le RLS fonctionne. Répète pour chaque table sensible et avec un utilisateur normal authentifié pour confirmer qu'il ne voit que ses données. Scanaris fait cette vérification pour toi : il détecte l'URL et la clé Supabase dans ton bundle, sonde les tables habituelles sans session, cherche la clé service_role et la chaîne de connexion dans le JavaScript, vérifie où ton appli stocke le jeton de session et te donne un fix prompt pour chaque constat. Si Lovable a généré ton appli, regarde aussi le guide de sécurité pour Lovable ; si le problème, ce sont les clés côté client, les secrets que ton appli laisse fuiter dans le navigateur.

Questions fréquentes

C'est un problème que la clé anon soit dans le navigateur ?

Non. Elle est conçue pour ça et n'identifie que le projet. Ce qui protège les données, c'est le RLS. Celle qui ne doit jamais être dans le navigateur, c'est service_role (ou la nouvelle clé secret).

Les nouvelles clés publishable et secret changent-elles quelque chose ?

Elles changent le format et permettent de les faire tourner sans toucher au JWT, mais le modèle est le même : la publishable est publique et passe par le RLS ; la secret le contourne et vit sur le serveur. Les anciennes clés continuent de fonctionner tant que tu ne les désactives pas.

Si mon appli a été faite par Lovable ou Bolt, dois-je quand même ouvrir le tableau de bord Supabase ?

Oui. L'outil génère le schéma, mais RLS, policies, buckets et réglages Auth sont à toi. Dix minutes avec les trois requêtes ci-dessus et le Security Advisor te disent dans quel état est le projet.

Vérifications liées

Ton app Supabase passe-t-elle ces vérifications ?

Analyse-la gratuitement et reçois un fix prompt pour chaque problème qu'on trouve.

Analyser mon site gratuitement