FixVibe

// docs / baas security / supabase storage

Liste de contrôle de sécurité des buckets Supabase Storage : 22 éléments

Supabase Storage est une fine couche au-dessus d'un bucket compatible S3 plus le même modèle de sécurité au niveau des lignes que la base de données. Cela signifie que les mêmes pièges RLS qui affectent les tables affectent l'accès aux fichiers — plus quelques-uns spécifiques au stockage qui apparaissent lorsque les outils de codage IA câblent les téléchargements. Cette liste de contrôle compte 22 éléments en cinq sections : configuration des buckets, politiques RLS, validation des téléchargements, URL signées et hygiène opérationnelle. Chacun est vérifiable en moins de 15 minutes.

Chaque élément ci-dessous est essentiel. Pour les mécanismes RLS sous-jacents, voir Scanner RLS Supabase. Pour la classe d'exposition de clés adjacente au stockage, voir Clé de rôle de service Supabase exposée en JavaScript.

Configuration des buckets

Commencez avec les bons défauts. Un bucket mal configuré fuit des fichiers, que votre RLS soit correct ou non.

  1. Mettez chaque bucket par défaut en privé. Dans le tableau de bord Supabase → Storage → Buckets, désactivez le bouton Public bucket sauf si vous avez une raison explicite (assets marketing, avatars publics sans PII). Les buckets publics contournent RLS pour les opérations de lecture — quiconque connaît le nom du bucket peut lister et télécharger.
  2. Définissez une limite de taille de fichier stricte sur chaque bucket. Tableau de bord → Paramètres du bucket → Limite de taille de fichier. 50 Mo est un défaut raisonnable pour les uploads utilisateur ; augmentez-la délibérément pour les cas d'usage vidéo / gros fichiers. Sans limite, un seul upload malveillant peut épuiser votre quota de stockage ou votre bande passante mensuelle.
  3. Restreignez les types MIME autorisés par bucket. Liste de types MIME autorisés — allowlist explicite, pas blocklist. image/jpeg, image/png, image/webp pour les buckets d'images uniquement. N'autorisez jamais text/html, application/javascript ou image/svg+xml dans un bucket de contenu utilisateur — ils s'exécutent dans le navigateur lorsqu'ils sont servis via URL signée.
  4. Utilisez un bucket par type de contenu, pas un bucket partagé. Les paramètres par bucket (taille, types MIME, politiques RLS) sont la granularité dont vous disposez. Un bucket user-avatars, un bucket document-uploads et un bucket public-assets sont plus faciles à verrouiller qu'un bucket mixte unique.
  5. Vérifiez la configuration CORS si uploads depuis le frontend. Si les utilisateurs téléchargent directement depuis le navigateur vers une URL signée, le CORS du bucket doit lister votre origine de production. * n'est acceptable que pour les buckets publics — jamais pour les buckets contenant des PII utilisateur.

Politiques RLS sur storage.objects

Supabase Storage stocke les métadonnées de fichier dans la table storage.objects. RLS sur cette table contrôle qui peut lire, télécharger, mettre à jour ou supprimer des fichiers. Sans RLS, le drapeau public/privé du bucket est votre seule protection.

  1. Confirmez que RLS est activé sur storage.objects. SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects'; doit retourner true. Supabase l'active par défaut sur les nouveaux projets ; vérifiez qu'il n'a pas été désactivé.
  2. Rédigez une politique SELECT limitée à auth.uid() pour les buckets privés. CREATE POLICY "users_read_own_files" ON storage.objects FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);. La convention est de stocker les fichiers sous [user-id]/[filename] et d'utiliser storage.foldername() pour extraire le propriétaire du chemin.
  3. Rédigez une politique INSERT qui applique la même convention de chemin. CREATE POLICY "users_upload_own" ON storage.objects FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);. Sans WITH CHECK, un utilisateur authentifié peut téléverser dans le dossier d'un autre utilisateur.
  4. Ajoutez des politiques UPDATE et DELETE si votre application prend en charge les modifications ou suppressions de fichiers. Chaque commande a besoin de sa propre politique. Sauter DELETE signifie que les utilisateurs authentifiés ne peuvent pas supprimer leurs propres fichiers ; sauter UPDATE signifie que les écrasements de fichiers échouent silencieusement.
  5. Testez l'accès inter-utilisateurs dans deux sessions de navigateur. Connectez-vous comme utilisateur A, téléversez un fichier, copiez le chemin. Connectez-vous comme utilisateur B dans un autre navigateur, essayez de récupérer le fichier via l'API REST. La réponse doit être 403 ou 404, jamais 200.
sql
-- Confirm RLS on storage.objects
SELECT rowsecurity
FROM   pg_tables
WHERE  schemaname = 'storage' AND tablename = 'objects';

-- SELECT policy: scope reads to the owning user's folder.
CREATE POLICY "users_read_own_files"
  ON storage.objects
  FOR SELECT
  USING (auth.uid()::text = (storage.foldername(name))[1]);

-- INSERT policy: enforce the [user-id]/[filename] path convention.
CREATE POLICY "users_upload_own"
  ON storage.objects
  FOR INSERT
  WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);

Validation des uploads

Validez chaque upload côté serveur, même lorsque le bucket a des contraintes MIME et de taille. Les outils de codage IA génèrent par défaut une validation uniquement côté client ; cela ne protège rien.

  1. Revérifiez le type MIME côté serveur à partir des octets réels du fichier, pas de l'en-tête Content-Type. Utilisez une bibliothèque comme file-type (Node) ou du sniffing d'octets magiques. Un attaquant peut déclarer Content-Type: image/jpeg sur un fichier qui est en réalité un payload polyglotte HTML / JavaScript.
  2. Supprimez les métadonnées EXIF des images téléversées. EXIF peut contenir des coordonnées GPS, des numéros de série d'appareils et des horodatages. Utilisez sharp avec .withMetadata(false) ou exif-parser pour les supprimer avant stockage.
  3. Rejetez les SVG qui contiennent des balises script ou des gestionnaires onload. SVG est du XML — et beaucoup d'applications générées par IA autorisent les uploads SVG comme « juste une image ». Utilisez DOMPurify côté serveur ou refusez entièrement les uploads SVG.
  4. Utilisez des noms de fichiers déterministes et non devinables. Ne conservez pas le nom de fichier original. Utilisez un UUID ou un hash du contenu du fichier. Les noms originaux fuient ("passport_scan_2024_01_15.jpg") et les noms prévisibles permettent l'énumération.

URL signées

Les URL signées sont la façon dont les clients accèdent aux buckets privés. L'expiration, la portée du bucket et ce qui est journalisé comptent.

  1. Par défaut, l'expiration des URL signées à 1 heure ou moins. La fonction createSignedUrl(path, expiresIn) du SDK JS Supabase prend des secondes. N'utilisez jamais des valeurs comme 31536000 (un an) — l'URL devient un lien semi-public permanent.
  2. Ne stockez jamais d'URL signées dans votre base de données. Générez-en de nouvelles côté serveur à chaque requête. Une URL signée stockée avec une expiration d'un an qui fuit via un dump de base de données accorde un accès à long terme.
  3. Journalisez la génération des URL signées, pas seulement les uploads de fichiers. Si vous suspectez plus tard une compromission, vous devez savoir qui a généré quelle URL et quand. Journalisez auth.uid() + bucket + chemin de l'objet + horodatage.
  4. Utilisez l'option downloadAs en servant des fichiers téléversés par les utilisateurs. createSignedUrl(path, expiresIn, { download: '.jpg' }) force un en-tête Content-Disposition: attachment pour que le fichier soit téléchargé au lieu d'être rendu — défait la classe d'exécution HTML / SVG / HTML-dans-PDF.

Hygiène opérationnelle

La configuration du stockage dérive au fil du temps. Ces quatre éléments opérationnels maintiennent la surface serrée.

  1. Auditez les buckets trimestriellement. Tableau de bord → Storage → Buckets. Confirmez que l'état public/privé et les listes de types MIME correspondent à ce que l'application attend. Les buckets créés « temporairement » deviennent permanents si personne ne les supprime.
  2. Surveillez les opérations de liste anonymes. Les logs de stockage (Tableau de bord → Logs → Storage) enregistrent les requêtes LIST. Un pic de requêtes de liste anonymes contre un bucket privé signifie que quelqu'un le sonde de l'extérieur.
  3. Définissez une politique de rétention pour les uploads éphémères. Les buckets temporaires (aperçu d'image, brouillons d'upload) devraient s'auto-supprimer après 24-72 heures via une fonction planifiée. La rétention indéfinie est un passif au titre des obligations de minimisation des données RGPD / CCPA.
  4. Lancez un scan FixVibe mensuellement. Le check baas.supabase-storage-public sonde les buckets qui répondent aux GET + LIST anonymes. De nouveaux buckets s'ajoutent ; les anciens changent de visibilité — seul un scan continu attrape la dérive.

Étapes suivantes

Lancez un scan FixVibe contre votre URL de production — les listings de stockage anonymes apparaissent sous baas.supabase-storage-public. Associez cette liste de contrôle à Scanner RLS Supabase pour la couche des tables et Clé de rôle de service Supabase exposée en JavaScript pour l'adjacence d'exposition de clés. Pour les mauvaises configurations de stockage chez d'autres fournisseurs BaaS, voir Scanner de mauvaises configurations BaaS.

// scannez votre surface baas

Trouvez la table ouverte avant qu'un autre ne le fasse.

Entrez une URL de production. FixVibe énumère les fournisseurs BaaS avec lesquels votre application communique, identifie leurs endpoints publics et signale ce qu'un client non authentifié peut lire ou écrire. Gratuit, sans installation, sans carte.

  • Offre gratuite — 3 scans / mois, sans carte d'inscription.
  • Identification BaaS passive — aucune vérification de domaine requise.
  • Supabase, Firebase, Clerk, Auth0, Appwrite et plus.
  • Prompts de correction IA sur chaque résultat — collez-les dans Cursor / Claude Code.
Lancer un scan BaaS gratuit

aucune inscription requise

Liste de contrôle de sécurité des buckets Supabase Storage : 22 éléments — Docs · FixVibe