← Retour au blog
SécuritéScanaris Team · 8 min de lecture · October 03, 2026

Sécurité Supabase : ce qu'il faut vérifier avant de lancer

Supabase te donne une base de données et une API en quelques minutes, et c'est justement là le danger : l'API est exposée dès la première seconde. Si tu ne configures pas la sécurité, n'importe qui disposant de ta clé publique peut lire et écrire dans tes tables. Ce guide passe en revue, dans l'ordre où il vaut mieux le faire, tout ce qu'il faut vérifier avant d'ouvrir ton app au monde : le RLS et les politiques avec du SQL d'exemple, quelle clé va où, le Storage, les vues, la configuration d'Auth et comment vérifier à la fin que c'est vraiment fermé.

Comment Supabase expose tes données (et pourquoi c'est une bonne chose)

Devant ton Postgres se trouve une couche (PostgREST) qui transforme chaque table du schéma public en endpoint HTTP. Ton app dans le navigateur lui parle directement avec une clé publique. Ça t'évite d'écrire un backend, mais ça veut dire qu'aucun serveur à toi ne se trouve entre les deux pour vérifier les permissions : les politiques de Row Level Security sont ton backend. Si elles sont bonnes, le design est solide ; si elles manquent, la table entière est à une URL de distance.

1. Le RLS activé sur TOUTES les tables

Le Row Level Security est la barrière principale de Supabase. Sans lui, la clé publique peut interroger des tables entières depuis le navigateur. L'erreur classique du vibe coder, c'est de créer une nouvelle table à la va-vite, ou de laisser l'IA la créer, et d'oublier d'activer le RLS. C'est exactement la cause de la vulnérabilité de Lovable (CVE-2025-48757).

Vérifie table par table dans le Table Editor : celles qui affichent « RLS disabled » sont ouvertes au public. Pour ne pas dépendre de ton œil, cette requête dans le SQL Editor liste celles qui manquent :

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

Et pour chacune :

alter table public.nom_de_la_table enable row level security;

L'activer ne suffit pas : une table avec le RLS actif mais sans politiques refuse tout (ton app cesse de fonctionner jusqu'à ce que tu les écrives), et une table avec une politique trop permissive ne protège rien.

2. Des politiques qui reflètent vraiment qui peut faire quoi

Une politique using (true) pour tout le monde laisse la table ouverte même avec le RLS actif ; l'IA les propose souvent « pour que ça marche ». Écris des politiques précises et sépare les règles par opération, parce que parfois tu veux autoriser la lecture mais pas la suppression.

Modèle A : à chaque utilisateur ses lignes

create policy "notes: lire les siennes" on public.notes
  for select to authenticated
  using ((select auth.uid()) = user_id);

create policy "notes: créer les siennes" on public.notes
  for insert to authenticated
  with check ((select auth.uid()) = user_id);

create policy "notes: modifier les siennes" on public.notes
  for update to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);

Le with check est ce qui empêche quelqu'un d'insérer ou de modifier une ligne au nom d'un autre utilisateur. Envelopper auth.uid() dans un (select …) fait que Postgres l'évalue une fois par requête et non une fois par ligne ; sur de grosses tables, la différence est énorme. Mets un index sur user_id.

Modèle B : appartenance à une équipe ou une organisation

create policy "projects: membres de l'équipe" on public.projects
  for select to authenticated
  using (
    exists (
      select 1 from public.team_members m
      where m.team_id = projects.team_id
        and m.user_id = (select auth.uid())
    )
  );

La table team_members a aussi besoin de ses politiques, et il vaut mieux que seul un administrateur puisse y insérer.

Modèle C : contenu public en lecture seule

create policy "posts: lecture publique" on public.posts
  for select to anon, authenticated
  using (published = true);

C'est légitime. Ce qui ne doit pas exister, c'est une politique d'écriture ouverte ou des colonnes avec des données personnelles dans une table lisible publiquement.

3. anon, service_role et les nouvelles clés

La clé anon est publique et va dans le frontend : elle est faite pour être encadrée par le RLS. La clé service_role contourne complètement le RLS et a un accès total. Elle ne doit jamais, au grand jamais, apparaître dans le navigateur ni dans une variable NEXT_PUBLIC_, VITE_ ou NUXT_PUBLIC_ : ce préfixe signifie « ceci est envoyé au client ».

Supabase a aussi introduit une nouvelle paire de clés dont les noms ne laissent aucun doute : sb_publishable_… (pour le navigateur, soumise au RLS) et sb_secret_… (serveur uniquement, contourne le RLS, révocable séparément). Si tu démarres un projet aujourd'hui, utilise-les ; si tu en as un ancien, migre quand tu peux. N'utilise la clé secrète que côté serveur, dans des Edge Functions ou des backends de confiance, et si tu soupçonnes une fuite, fais-la tourner immédiatement depuis le tableau de bord.

4. Storage : les buckets ont aussi des règles

Le stockage de fichiers utilise ses propres politiques, sur la table storage.objects. Un bucket public sert n'importe quel fichier par URL à quiconque la possède, pour toujours. Décide consciemment quels buckets sont publics (avatars, images produit) et protège le reste. Un exemple pour que chaque utilisateur ne lise et n'envoie que dans son propre dossier :

create policy "documents: dossier personnel" on storage.objects
  for all to authenticated
  using (
    bucket_id = 'documents'
    and (storage.foldername(name))[1] = (select auth.uid())::text
  )
  with check (
    bucket_id = 'documents'
    and (storage.foldername(name))[1] = (select auth.uid())::text
  );

Envoie les documents privés dans des buckets privés et sers-les avec des URL signées de courte durée, pas dans un bucket public avec des noms difficiles à deviner.

5. Vues, fonctions et schémas exposés

Trois endroits par où les données s'échappent même quand les tables sont bien configurées :

  • Les vues. Dans Postgres, une vue s'exécute avec les permissions de celui qui l'a créée, donc une vue sur une table protégée peut renvoyer des lignes que les politiques interdiraient. Crée-les avec with (security_invoker = true) (Postgres 15 ou plus) ou vérifie-les une par une.
  • Les fonctions RPC. Une fonction security definer s'exécute avec les permissions de son propriétaire. Ne l'utilise que quand tu en as vraiment besoin, valide les paramètres à l'intérieur et limite qui peut l'appeler avec revoke execute … from anon.
  • Les schémas exposés. Dans Settings → API, tu choisis quels schémas l'API publie. Ce qui ne doit pas sortir en HTTP ne doit pas être dans cette liste.

6. La configuration d'Auth à vérifier

  • Confirmation d'email activée, pour que personne ne crée de compte avec l'adresse de quelqu'un d'autre.
  • Limites de tentatives de connexion et d'envoi d'emails : elles sont activées par défaut ; vérifie que personne ne les a augmentées « pour tester ».
  • URL de redirection autorisées uniquement vers tes domaines ; une liste ouverte permet d'utiliser ta page de connexion pour rediriger vers des sites de phishing.
  • Fournisseurs OAuth : uniquement ceux que tu utilises, avec l'écran de consentement au nom de ta marque.
  • Expiration des liens magiques et des OTP courte ; la valeur par défaut est raisonnable, ne l'allonge pas.

7. Secrets et clés hors du frontend

Au-delà de la clé secrète de Supabase, vérifie qu'aucune clé tierce (Stripe, email, IA) n'est embarquée dans le JavaScript côté client. L'IA les met parfois là pour faire marcher quelque chose rapidement, et elles restent visibles pour toujours dans le bundle. Les appels aux services payants passent par une Edge Function ou par ton serveur, jamais par le navigateur. Voir les secrets que ton app laisse fuir dans le navigateur.

8. Environnements : le développement n'est pas la production

Utilise un projet Supabase distinct pour le développement, avec des données inventées. Un projet de test avec une copie des vraies données et des politiques à moitié faites est tout aussi exposé que la production, et personne ne le surveille.

Comment vérifier que c'est fermé (le test final)

  • Test en navigation privée. Avec l'app ouverte, copie depuis F12 → Network un appel à /rest/v1/ et ouvre-le sans session. Il doit renvoyer une liste vide ou une erreur.
  • Test du « demander plus ». Connecté, retire le filtre de l'URL (?user_id=eq.…). Seules tes lignes doivent revenir.
  • Test des deux comptes. Crée deux utilisateurs et confirme qu'aucun ne voit, ne modifie ni ne supprime les données de l'autre, fichiers du Storage compris.
  • Security Advisor. Dans le tableau de bord, la section Advisors liste les tables sans RLS, les politiques permissives et les fonctions exposées. Garde-la au vert.

Scanaris vérifie ton Supabase pour toi

Scanaris inclut un audit optionnel de Supabase qui, avec une connection string en lecture seule, vérifie table par table si le RLS est activé et si des politiques existent, et dans le scan normal détecte les clés exposées dans le frontend, le CORS permissif et les endpoints qui servent des données sans session. Chaque résultat vient avec un prompt prêt à coller dans ton IA. Lance-le avant de publier et tu sauras si tu as laissé une porte ouverte ; active la surveillance et il te préviendra si l'IA en rouvre une dans une nouvelle table.

Questions fréquentes

Puis-je me passer du RLS en mettant mon propre backend devant ?

Oui, en n'utilisant que la clé secrète depuis ton serveur et jamais la clé publique côté client. Mais alors ton backend doit faire toutes les vérifications de permissions que le RLS ferait. Pour la plupart des apps, le RLS demande moins de travail et est plus difficile à casser.

Le RLS nuit-il aux performances ?

À peine, si les politiques sont bien écrites : (select auth.uid()) au lieu de auth.uid(), des index sur les colonnes utilisées par les politiques et pas de sous-requêtes lourdes. Une politique mal écrite peut en revanche ralentir une grosse table.

Et les tables que l'IA crée après le lancement ?

Elles naissent sans RLS. Ajoute « active le RLS et écris les politiques » à ta routine (ou au prompt de ton IA) chaque fois qu'une nouvelle table apparaît, et utilise un re-scan automatique pour savoir si ça a été oublié.

Comment se porte ton site ?

Passe-lui Scanaris et vérifie en 30 secondes, c'est gratuit.

Analyser mon site

Vérifications liées

Guides de sécurité par stack