// 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.
- 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.
- 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.
- Restreignez les types MIME autorisés par bucket. Liste de types MIME autorisés — allowlist explicite, pas blocklist.
image/jpeg,image/png,image/webppour les buckets d'images uniquement. N'autorisez jamaistext/html,application/javascriptouimage/svg+xmldans un bucket de contenu utilisateur — ils s'exécutent dans le navigateur lorsqu'ils sont servis via URL signée. - 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 bucketdocument-uploadset un bucketpublic-assetssont plus faciles à verrouiller qu'un bucket mixte unique. - 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.
- Confirmez que RLS est activé sur storage.objects.
SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects';doit retournertrue. Supabase l'active par défaut sur les nouveaux projets ; vérifiez qu'il n'a pas été désactivé. - 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'utiliserstorage.foldername()pour extraire le propriétaire du chemin. - 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. - 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.
- 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
403ou404, jamais200.
-- 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.
- 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 commefile-type(Node) ou du sniffing d'octets magiques. Un attaquant peut déclarerContent-Type: image/jpegsur un fichier qui est en réalité un payload polyglotte HTML / JavaScript. - 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
sharpavec.withMetadata(false)ouexif-parserpour les supprimer avant stockage. - Rejetez les SVG qui contiennent des balises
scriptou des gestionnairesonload. SVG est du XML — et beaucoup d'applications générées par IA autorisent les uploads SVG comme « juste une image ». UtilisezDOMPurifycôté serveur ou refusez entièrement les uploads SVG. - 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.
- 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 comme31536000(un an) — l'URL devient un lien semi-public permanent. - 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.
- 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. - Utilisez l'option
downloadAsen servant des fichiers téléversés par les utilisateurs.createSignedUrl(path, expiresIn, { download: '.jpg' })force un en-têteContent-Disposition: attachmentpour 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.
- 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.
- 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. - 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.
- Lancez un scan FixVibe mensuellement. Le check
baas.supabase-storage-publicsonde les buckets qui répondent auxGET+LISTanonymes. 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.
