Aller au contenu
Bastide Connexion Créer un compte
Appuyez sur / pour chercher

Sécurité des données

EnglishMarkdownv0.3.0

Cette page dit comment protéger les lignes de votre base contre ceux qui tiennent la clé publiable : les politiques de sécurité au niveau des lignes (RLS), l'épreuve d'une politique sous l'identité d'un utilisateur, le classement des colonnes personnelles que les agents ne lisent pas, et l'analyse quotidienne de la posture de sécurité.

Le principe

La clé publiable est dans le code de votre navigateur : n'importe qui peut la lire. Ce qui protège vos données, ce sont donc les politiques que la base applique à chaque requête, pas l'application.

  • Une table n'est servie par l'API que si elle porte la sécurité au niveau des lignes ET au moins une politique. Les droits (select, insert, update, delete pour les rôles anonyme et authentifié) suivent d'office, et tombent dès que la table perd l'une ou l'autre (LIM-11). Supabase, lui, accorde toutes les tables de public.
  • Les rôles portent le nom de la base en préfixe. Un grant … to authenticated ou un create policy … to authenticated écrit tel quel est refusé (rôle inconnu, 42704). Écrivez la politique sans TO : elle vaut pour tous les rôles, et auth.uid() distingue l'utilisateur connecté. auth.role() et auth.jwt() rendent, eux, les noms de Supabase.
  • **Écrivez (select auth.uid()), pas auth.uid() nu.** La forme en sous-requête est évaluée une fois par requête, au lieu d'une fois par ligne. La plateforme refuse la forme nue et rend sa réécriture.

Les politiques proposées

Pour chaque table du schéma public, la plateforme engendre une proposition, sans modèle d'IA, d'après la structure de la table. Trois régimes, essayés dans cet ordre :

RégimeQuandCe qui est proposé
PropriétaireUne colonne uuid rattache la ligne à un utilisateur : une clé étrangère vers auth.users(id), sinon un nom de colonne conventionnel.Chacun voit et modifie ses lignes.
Par le parentAucune colonne, mais une clé étrangère mène à une table qui appartient à quelqu'un (lignes de commande, tables de liaison).La ligne appartient au propriétaire de son parent.
FerméeNi l'un ni l'autre.La table est fermée aux utilisateurs et ouverte à la seule clé de service.

La proposition porte les droits, quatre politiques (une par commande) pour le rôle authentifié, une politique pour la clé de service, et l'index qui sert le prédicat. Le rôle anonyme ne reçoit rien. Ouvrir une table en lecture à tous (USING (true)) n'est jamais proposé : c'est une décision qui vous revient.

L'épreuve avant la validation

Avant de valider une politique, la plateforme l'éprouve sous quatre identités, avec de vrais utilisateurs de la base : un visiteur anonyme, le propriétaire d'une ligne, un autre utilisateur, et la clé de service. Une ligne lisible par qui ne la possède pas est un défaut : la politique ne s'applique alors qu'après la confirmation d'une personne, et le rapport cite la ligne.

Depuis la console

Ouvrez Politiques (depuis Données). Les tables sont à gauche, avec leur état ; la table choisie montre au centre sa politique, sa forme et la proposition ; à droite, de quoi l'éprouver sous l'identité d'un utilisateur.

  1. Choisissez une table et relisez la proposition, ou écrivez votre propre prédicat.
  2. Appliquez : le rapport des quatre identités s'affiche.
  3. Si une ligne est vue sans être possédée, confirmez ou corrigez.

Désactiver la sécurité au niveau des lignes d'une table est un autre geste, du rang admin : il se confirme en deux temps, en retapant le nom de la table, et l'ouvrier de la plateforme l'exécute (database.rls.configure). Réactiver ne se confirme pas. Ce geste est fermé aux agents.

Par un agent, la ligne de commande ou l'API

GesteOutil MCPLigne de commande
Lire les propositionsdatabase_policy_listbastide database policy list
Appliquer la proposition d'une tabledatabase_policy_write_proposedbastide database policy write_proposed
Appliquer votre prédicatdatabase_policy_writebastide database policy write
Activer ou désactiver la RLSaucunbastide database rls configure

Appliquer un prédicat d'appartenance (rang maintainer, portée db:write) :

{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "table": "commandes",
  "policy": "proprietaire_id = (select auth.uid())",
  "verifyAs": ["anon", "authenticated", "service_role"],
  "idempotencyKey": "politique-commandes-2026-09-14"
}

Si l'épreuve trouve une ligne indûment lisible, l'appel est refusé sous confirmation_requise avec un nonce ; une personne le rejoue avec confirmationNonce. La réponse porte applied, rewrite (la forme optimisée, si la vôtre a été refusée), verifications, report et indexCreated. Une politique se remplace par un nouvel appel, qui retire la précédente avant de poser la sienne.

Éprouver une politique sous l'identité d'un utilisateur

L'usurpation montre ce qu'un utilisateur précis de votre application voit vraiment.

  1. Ouvrir une session pour un utilisateur de auth.users, désigné par son identifiant (database_impersonation_create). Elle dure trente minutes, sans prolongation. L'audit garde qui l'a ouverte et pour quel utilisateur.
  2. Lire sous son identité (database_impersonation_read) : une instruction, sous le rôle authentifié, en transaction lecture seule, cent lignes au plus. denied dit si la base a refusé la lecture.
  3. Écrire sous son identité (database_impersonation_write) : une écriture réelle, qui ne part qu'après la confirmation d'une personne en deux temps. Les politiques s'y appliquent comme pour l'utilisateur.

Dans la console, le panneau de droite de Politiques fait les mêmes gestes : choisissez l'utilisateur, ouvrez la session, puis lisez. Le bouton d'écriture ne l'exécute pas directement : il ouvre une fenêtre qui dit ce qui va se passer avant que vous ne validiez.

{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "impersonationId": "usu_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "sql": "select id, total from commandes order by id"
}

Les colonnes personnelles

Vous pouvez classer des colonnes « donnée personnelle » : adresse, téléphone, date de naissance. Une colonne classée ne se lit par aucun agent : toute instruction d'un agent dont le plan la lit, l'écrit ou la passe à une fonction du projet est refusée sous colonne_personnelle_refusee, dans l'éditeur SQL comme sous usurpation. Une personne la lit toujours depuis la console.

  • La liste vit au plan de contrôle : la base n'est pas touchée, et une colonne peut être classée avant d'exister.
  • Elle se remplace entière : ce qui n'y est plus est déclassé. Cinq cents colonnes au plus.
  • Seule une personne la règle (rang maintainer) : déclasser une colonne l'ouvrirait aux agents, et l'opération database.personal_columns.configure leur est donc fermée.
  • Un projet créé par un agent et pas encore revendiqué ne reçoit aucune donnée personnelle (projet_non_revendique).

Dans la console : Données, section « Sécurité », carte des colonnes personnelles. Par la ligne de commande : bastide database personal_columns configure. Par l'API : PUT /v1/projects/{projectId}/database/personal-columns.

{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "columns": [
    { "schema": "public", "table": "clients", "column": "courriel" },
    { "schema": "public", "table": "clients", "column": "telephone" }
  ],
  "idempotencyKey": "classement-clients-2026-10-13"
}

La posture de sécurité

Chaque jour, la plateforme analyse votre base et relève cinq genres de défauts, du plus grave au moins grave :

ConstatEnjeuRemède
Table exposée sans politiqueQuiconque tient la clé publiable lit ou écrit toutes ses lignes.Appliquer la politique proposée.
Colonne personnelle lisible de la clé publiableUne colonne classée est lue par le rôle anonyme.Retirer le droit de lecture au rôle anonyme.
Clé secrète vue d'un navigateurSon secret est dans le code d'une page.Frapper une clé neuve côté serveur, puis révoquer celle-ci.
Politique toujours vraieElle ouvre la table aux rôles de l'API sans condition.La remplacer par un prédicat d'appartenance.
Politique sans indexChaque ligne réévalue le prédicat.Créer l'index proposé.

Chaque constat porte sa correction, et la posture les réunit en une migration que vous appliquez (voir Branches et migrations). Ce que l'analyse n'a pas pu mesurer est dit comme tel (notMeasured), jamais comme une base saine.

  • Console : Données, section « Sécurité » ; la colonne de droite de la section « Tables » en donne le résumé.
  • Agent : database_posture_read ; analyzedAt nul veut dire qu'aucune analyse n'a encore eu lieu.
  • Correction en demande de fusion : activée, chaque correction nouvelle part sur une branche bastide/… de votre dépôt GitHub, en demande de fusion ; rien n'est poussé sur votre branche. Désactivée par défaut, elle se règle depuis la console ou par database.posture.configure, fermée aux agents.

Erreurs fréquentes

  • Une table renvoie zéro ligne par l'API : elle n'a pas de politique, ou sa politique n'accorde rien à ce rôle. Lisez Politiques.
  • **42704** : vous avez nommé authenticated ou anon ; retirez le TO.
  • La politique est refusée pour sa forme : reprenez la réécriture rendue dans rewrite.
  • **confirmation_requise** : l'épreuve a trouvé une ligne indûment lisible ; lisez le rapport avant de confirmer.
  • **usurpation_expiree** : la session a passé trente minutes ; ouvrez-en une autre.
  • **colonne_personnelle_refusee** : la colonne est classée ; une personne la lit depuis la console.

Voir aussi

Signaler une erreur sur cette page