Supabase12 items

Supabase Storage Security Checklist

Checklist for securing Supabase Storage buckets

Last updated 2026-01-15

Quick Checklist

  • 1Audit all buckets for public vs. private classification
  • 2Set RLS policies on the storage.objects table
  • 3Restrict file uploads by MIME type
  • 4Enforce maximum file size limits
  • 5Organize files into user-scoped folder paths
  • 6Validate file names to prevent path traversal
  • 7Use signed URLs for time-limited access to private files
  • 8Disable public bucket access for sensitive content
  • 9Configure CORS for Storage endpoints
  • 10Monitor storage usage for abuse
  • 11Set up lifecycle rules for temporary uploads
  • 12Test download policies with different user contexts

Supabase Storage Security Checklist

Supabase Storage provides S3-compatible object storage with RLS integration. Misconfigured buckets are a common source of data leaks, especially when storing user-uploaded content like profile pictures, documents, or media files.

1. Bucket Classification

Supabase Storage supports two bucket types:

  • Public buckets: Files can be accessed by anyone with the URL. No authentication required. Suitable for public assets like logos or marketing images.
  • Private buckets: Files require authenticated access through RLS policies. Required for user uploads, documents, and any sensitive content.

Audit every bucket and confirm its classification:

SELECT id, name, public FROM storage.buckets;

If a bucket is public, verify that it truly should be. A common mistake is creating a public bucket for "convenience" during development and forgetting to change it.

2. RLS Policies on storage.objects

Storage access is controlled through RLS policies on the storage.objects table. Without explicit policies on a private bucket, no one can access the files (secure default). However, overly broad policies are dangerous:

-- DANGEROUS: Anyone can read all files in the bucket
CREATE POLICY "Public read" ON storage.objects
  FOR SELECT USING (bucket_id = 'uploads');

-- CORRECT: Users can only read their own files
CREATE POLICY "User read own files" ON storage.objects
  FOR SELECT USING (
    bucket_id = 'uploads'
    AND auth.uid()::text = (storage.foldername(name))[1]
  );

The convention is to organize files into folders named after the user's UUID: uploads/{user_id}/filename.jpg. This makes RLS policies straightforward and predictable.

3. Upload Restrictions

Without restrictions, attackers can:

  • Upload extremely large files to consume storage quota and increase costs.
  • Upload executable files or HTML files that could be served as web pages (stored XSS).
  • Upload files with malicious names to attempt path traversal.

Enforce restrictions in your RLS policy and application code:

-- Restrict uploads to images under 5MB
CREATE POLICY "Users can upload images" ON storage.objects
  FOR INSERT WITH CHECK (
    bucket_id = 'avatars'
    AND auth.uid()::text = (storage.foldername(name))[1]
    AND (storage.extension(name)) IN ('jpg', 'jpeg', 'png', 'webp')
    AND (metadata->>'size')::int < 5242880
  );

Additionally, validate MIME types server-side. Do not trust the Content-Type header from the client. Use magic byte detection when possible.

4. File Name Validation

Sanitize file names to prevent:

  • Path traversal: Names containing ../ could escape the intended folder.
  • Special characters: Names with null bytes, newlines, or unicode tricks can cause issues.
  • Name collisions: Use UUIDs or hash-based names to prevent one user from overwriting another user's file.
// Generate a safe file name
const safeName = `${crypto.randomUUID()}.${extension}`;
const path = `${user.id}/${safeName}`;
await supabase.storage.from('uploads').upload(path, file);

5. Signed URLs for Private Content

For private files that need to be shared temporarily (e.g., a download link in an email), use signed URLs with a short expiration:

const { data } = await supabase.storage
  .from('documents')
  .createSignedUrl('user-123/contract.pdf', 3600); // 1 hour expiry

Never generate signed URLs with excessively long expiration times. If a signed URL leaks, it grants access to anyone until it expires.

6. CORS Configuration

Configure CORS headers on your Supabase project to restrict which domains can make Storage API requests. This prevents malicious websites from using a logged-in user's session to upload or download files.

7. Monitoring and Lifecycle

  • Monitor storage usage per bucket and set up alerts for unusual growth (potential abuse).
  • Implement lifecycle rules or background jobs to clean up temporary uploads (e.g., incomplete multipart uploads, expired content).
  • Log all upload and download events for audit purposes.

8. Content-Disposition Headers

When serving user-uploaded files, set Content-Disposition: attachment to prevent browsers from rendering uploaded HTML or SVG files inline, which could lead to stored XSS attacks.

Use AuditYour.app to scan your Supabase project for open storage buckets and misconfigured storage policies.

Scan your app for this vulnerability

AuditYourApp automatically detects security misconfigurations in Supabase and Firebase projects. Get actionable remediation in minutes.

Run Free Scan