Aller au contenu

Bases de la sécurité

Votre bucket Supabase Storage est public. Est-ce un problème ?

Un bucket Supabase Storage public signifie que celui qui a l'URL d'un fichier peut l'ouvrir. Il ne signifie pas que quelqu'un peut lister ce qu'il contient.

Vlad Tkachenko8 min de lecture
Des documents dans un conteneur. Une barre couvre presque toute l'ouverture et s'arrête avant la fin, laissant quatre dans le vide.

En bref

  • Un bucket Supabase Storage public signifie une seule chose : celui qui détient l'URL d'un fichier peut l'ouvrir sans se connecter. Il ne dit rien sur ce qu'il peut voir d'autre à l'intérieur.
  • Le listage est accordé par une politique d'accès et non par l'interrupteur public, donc un bucket privé peut être listable et un bucket public ne pas l'être.
  • Le listage est ce qu'il faut corriger aujourd'hui, parce qu'il épargne à un inconnu la peine de deviner un seul nom de fichier.

Quelqu'un a ouvert votre application, y a fouillé une minute et vous a envoyé un message : votre bucket Supabase Storage est public, et n'importe qui peut lister ce qu'il contient.

Ce sont deux affirmations distinctes. L'une porte probablement sur un réglage que vous avez choisi exprès et que vous devriez garder. L'autre vaut un après-midi.

Voici ce que guide après guide raconte de travers : public et listable sont deux interrupteurs différents, et celui qui inquiète n'est pas celui que tout le monde vous dit de basculer. Passer un bucket en privé ferme le premier. Cela peut laisser le second grand ouvert, et un bucket qui n'a jamais été public de sa vie peut être listable cet après-midi.

Un bucket Supabase Storage public est-il un problème de sécurité ?

En soi, non. Cela signifie une chose bien précise, et cette chose est très souvent celle que vous vouliez.

Marquer un bucket comme public donne à chaque fichier qu'il contient une URL qui fonctionne sans connexion. C'est toute la fonctionnalité. C'est ainsi que l'avatar sous un commentaire se charge pour quelqu'un sans compte, que votre logo apparaît dans un e-mail, qu'une photo de produit s'affiche pour quelqu'un qui hésite encore à s'inscrire.

Un bucket public ressemble davantage à un numéro de téléphone sur liste rouge qu'à une porte non verrouillée. La ligne aboutit pour quiconque compose le numéro, et il n'existe aucun annuaire où le chercher. Que ce soit acceptable dépend d'une seule question : avec quelle facilité quelqu'un arriverait à ce numéro sans qu'on le lui donne.

Supabase crée les nouveaux buckets en privé et place un avertissement à côté de l'interrupteur. Un bucket public est donc quelque chose que quelqu'un a activé : vous, ou votre builder, pour faire fonctionner une fonction de téléversement.

Ce que l'interrupteur public active réellement

Une ligne du tableau ci-dessous, et rien d'autre dedans.

Ce que quelqu'un tenteBucket publicBucket privé
Ouvrir un fichier dont il a déjà l'URL exacteFonctionne, sans connexionExige une politique ou un lien signé
Demander la liste du contenu du bucketSeulement si une politique le permetSeulement si une politique le permet
Téléverser un fichierSeulement si une politique le permetSeulement si une politique le permet
Supprimer ou remplacer un fichierSeulement si une politique le permetSeulement si une politique le permet

La page d'aide de Supabase le dit aussi clairement que cela peut se dire : un bucket public signifie qu'il existe une URL publique permettant de télécharger le fichier, et toute autre opération doit encore satisfaire les politiques de ce bucket.

L'interrupteur est donc un contrôle plus petit que son nom ne le laisse croire. Trois de ces quatre lignes se décident ailleurs, sous Storage → Policies, et le message que vous avez reçu portait presque à coup sûr sur la seconde.

L'interrupteur du haut change une seule chose. La case en dessous décide si un inconnu obtient la liste, et elle le fait de la même façon que le bucket soit public ou privé.

Pourquoi un bucket privé peut rester listable

Parce que le listage est accordé par une politique d'accès, et que la politique que la plupart des gens collent l'accorde à tout le monde.

Toute lecture de Storage passe par Row Level Security sur une table nommée storage.objects. Lister un bucket est une lecture, classée sous SELECT, tout comme télécharger un fichier. Le guide de démarrage de Supabase montre une politique de cette forme, dont sortent la plupart des copies :

create policy "Public Access"
  on storage.objects for select
  using ( bucket_id = 'public' );

Lisez ce qu'elle dit. N'importe quelle requête peut lire n'importe quoi dans ce bucket. Elle ne demande pas qui demande, et elle ne demande pas si le fichier a le moindre rapport avec le demandeur. Lire couvre le téléchargement d'un fichier, et couvre la remise de la liste des fichiers, car c'est la même permission avec deux casquettes.

L'interrupteur du bucket n'entre jamais en jeu. Une politique pareille sur un bucket privé rend le bucket listable, et le tableau de bord continuera de le décrire, à juste titre, comme privé.

Le mot qui fait les dégâts est « n'importe quelle ». Une requête portant la clé publiable qui voyage dans le code de votre application satisfait cette politique sans effort, et cette clé est faite pour être lisible par tous, ce qui est précisément pourquoi la politique est censée être la partie qui travaille. C'est la même forme que le problème d'à côté, où Row Level Security est activée et laisse tout passer quand même.

Ce que quelqu'un tire d'une liste de vos fichiers

Des noms, surtout. Ce qui est bien plus que cela n'en a l'air.

Les noms de fichiers décrivent souvent leur contenu, parce que quelqu'un les a choisis en pensant à ce qu'il y avait dedans : facture-mars-acme.pdf, passeport-recto.jpg, paie-final-v2.xlsx. Une liste de ceux-là est un résumé correct de votre activité, et sa longueur dit à peu près combien de clients vous avez. Si le bucket est en plus public, chaque nom de cette liste est un lien qui fonctionne.

Retour au numéro de téléphone. Être sur liste rouge vaut quelque chose jusqu'au jour où l'annuaire paraît, et après cela il n'a jamais compté que le numéro soit difficile à deviner.

Sans listage, un inconnu doit arriver à un nom de fichier d'une manière ou d'une autre. Avec, on lui remet chacun des noms que vous avez.

La façon dont cela remonte est ordinaire. Un document appartenant à un client apparaît devant un autre. Quelqu'un vous cite le nom d'un fichier qu'il n'avait aucun moyen de connaître. Un dossier de téléversements est copié en entier par un robot automatique qui parcourait tous les projets Supabase qu'il pouvait trouver.

Notre scan gratuit demande un listage à votre Storage en n'utilisant rien d'autre que la clé déjà présente dans le code de votre application, et vous dit quels buckets ont répondu avec du contenu. Il lit des noms et ne télécharge jamais un fichier. Il prend une vingtaine de secondes et ne demande aucun compte : scannez votre application.

Comment savoir dans quel cas vous êtes

Deux vérifications dans le tableau de bord Supabase, et la seconde porte sur ce dont le message parlait vraiment.

Storage → Buckets. Les publics sont étiquetés comme publics. Pour chacun, demandez-vous si absolument chaque fichier qu'il contient est quelque chose que vous montreriez à un inconnu. Pas la plupart des fichiers. Chacun, y compris ce qui y atterrira la semaine prochaine depuis une fonction que vous n'avez pas encore construite.

Storage → Policies. Lisez chaque politique SELECT qui touche le bucket. Une condition qui ne mentionne que le nom du bucket laisse n'importe qui le lire. Une condition qui compare le propriétaire du fichier à l'auteur de la requête fait un vrai travail. Une liste de politiques vide sur un bucket privé signifie que rien n'est lu du tout, ce qui est restrictif et sûr.

Si votre application doit montrer un fichier privé à la bonne personne, l'outil pour cela est un lien signé : votre serveur demande à Supabase une URL qui fonctionne un nombre de minutes fixé puis cesse de fonctionner. Le fichier reste privé et arrive quand même sur la page.

Quand public est la bonne réponse

Plus souvent qu'un article de sécurité ne l'admet d'habitude, et cela mérite d'être dit.

Si le fichier est destiné à tout le monde, public est correct, et le contourner vous achète une application plus lente et davantage de code à entretenir. Avatars, logos, images de couverture, tout ce qu'un visiteur déconnecté est censé voir : dans un bucket public, et n'y pensez plus.

La seule chose à faire même là est de laisser le listage éteint. Un bucket public plein d'avatars est une fonctionnalité. Le même bucket qui remet la liste complète de tous ceux qui en ont un jour téléversé un est autre chose, et vous ne l'avez pas demandée.

Si le fichier appartient à une personne en particulier, il va dans un bucket privé avec une politique qui vérifie qui demande, et atteint la page par un lien signé. Le test est une question unique : pour qui est ce fichier ? Pour tout le monde, ou pour un compte nommé.

Ce qu'il faut faire cette semaine

Que faire

  • Ouvrez Storage → Buckets et notez lesquels sont publics. Pour chacun, décidez s'il contient des fichiers destinés à tous ou des fichiers destinés à une seule personne.
  • Lisez chaque politique SELECT sur storage.objects. Une condition qui ne nomme que le bucket remet la liste des fichiers à n'importe qui ; c'est elle qui a rendu vrai le message que vous avez reçu.
  • Déplacez tout ce qui appartient à un seul utilisateur dans un bucket privé et servez-le par des liens signés, qui expirent d'eux-mêmes.
  • Cessez de nommer les fichiers téléversés d'après leur contenu. Un identifiant aléatoire ne coûte rien et fait qu'une liste ayant fuité livre bien moins qu'elle ne l'aurait fait.
  • Sauvegardez votre Storage séparément de votre base de données. Vos téléversements ne sont ni dans votre code ni dans vos tables Postgres, donc rien de ce qui copie ces deux-là ne copie vos fichiers.

Ouvrez Storage → Policies dans votre projet et lisez ce qui s'y trouve avant de toucher au moindre interrupteur. Cet écran-là décide si la seconde moitié du message que vous avez reçu était vraie. La checklist de sécurité en 10 minutes le couvre à côté du reste de ce qu'une application fraîchement lancée a tendance à laisser ouvert, et si un bucket s'est révélé lisible, ce qu'un inconnu atteint dans votre base de données est la suite à lire, puisque la politique qui remet une liste de fichiers a la même forme que celle qui remet une table.

FAQ

On me dit que mon bucket Supabase Storage est public. Dois-je le passer en privé ?

Pas avant de savoir ce qu'il contient. Les buckets publics existent pour une raison : photos de profil, logos, images produit et tout ce que votre application montre à un visiteur non connecté. Pour ces fichiers, public est le bon réglage. Pour des factures, des pièces d'identité, des exports ou tout ce qui appartient à une personne en particulier, c'est le mauvais, et ce qu'il vous faut alors c'est privé plus des liens signés. La question n'a jamais été de savoir si public est mauvais. Elle est de savoir si ces fichiers-là étaient destinés à être vus par quiconque le demande.

Quelle est la différence entre un bucket public et un bucket listable ?

Public décide si un fichier s'ouvre pour quelqu'un qui a déjà son URL. Listable décide si un inconnu peut demander à votre projet le contenu entier d'un bucket et obtenir une réponse. Les deux se règlent à des endroits différents du tableau de bord Supabase, et le second transforme une devinette en annuaire.

Quelqu'un peut-il deviner les URL des fichiers de mon bucket public ?

Cela dépend entièrement de la façon dont votre application les nomme. Si elle les téléverse sous leur nom d'origine, ou sous quelque chose d'ordonné comme facture-4.pdf, alors oui, et cela demande très peu d'efforts. Si elle les téléverse sous un identifiant long et aléatoire, deviner devient impraticable. C'est exactement pour cela que le listage pèse si lourd : il supprime complètement l'étape de la devinette.

Comment empêcher qu'on liste mon bucket ?

Le listage passe par une politique Row Level Security sur la table storage.objects, c'est donc là que cela se corrige et non sur le bucket lui-même. Ouvrez Storage → Policies dans votre tableau de bord Supabase et lisez chaque politique SELECT qui touche le bucket. Une politique dont la seule condition est le nom du bucket laisse n'importe quelle requête le lire, liste des fichiers comprise. Resserrez-la pour qu'elle compare le fichier à la personne qui le demande, ou retirez-la et distribuez des liens signés depuis votre serveur.

Ma sauvegarde de base de données couvre-t-elle mes fichiers téléversés ?

Non. Storage vit en dehors de votre base Postgres, donc une sauvegarde de la base contient les lignes qui pointent vers vos fichiers et aucun des fichiers eux-mêmes. La restaurer vous rend une table pleine de liens vers des choses qui ne sont plus là. Vos téléversements doivent être copiés séparément, et la plupart des gens l'apprennent le jour où cela compte.

Écrit par

Vlad Tkachenko

Fondateur de Reeve

Je passe mes journées à examiner des applications créées avec Lovable, Bolt, v0, Cursor et Replit, et la courte liste d'erreurs qui y reviennent sans cesse.

En savoir plus sur l'auteur

À lire ensuite

Vous ne savez pas où en est votre propre application ?

Lancez une analyse gratuite et obtenez une note claire de A à F en une vingtaine de secondes. Sans compte, sans carte.

Analyser mon application

Vérification externe automatisée, pas un audit complet. L'absence de problème n'est pas une garantie de sécurité.