---
title: "Open storage buckets"
slug: open-storage-buckets
severity: critical
category: Database
summary: "A public bucket, or a storage policy of allow-all, lets anyone with the URL read files other users uploaded."
platforms:
  - Supabase
  - Firebase
  - Vercel
updated: "2026-10-13"
related:
  - unsafe-file-uploads
  - supabase-tables-without-rls
  - firebase-rules-left-open
---

Table RLS does not apply to files. Supabase Storage and Firebase Storage have their own policies. A bucket flagged public, or a policy of `allow read: if true`, means the object URL is the only secret. URLs leak through the network panel, emails, and referrers.

"Public" is appropriate for assets you meant to publish, such as marketing images. It is not appropriate for invoices, IDs, or user uploads you thought were private because the UI does not link to them.

Listing and reading are different. A bucket can hide the file list and still serve any object whose name is known. Generated names help. A private policy helps more.

## How to check in 5 minutes

1. In Supabase, open Storage and look at each bucket's public flag and policies. In Firebase, open Storage rules.
2. Copy a file URL from one test user. Open it in a private window with no login. If the file loads and it was not meant to be public, the bucket is open.
3. As a second test user, request the first user's object path through the client SDK. If the SDK returns it, the policy is too wide even when the bucket is marked private.
4. Search the repo for `getPublicUrl` and `public: true`. Those call sites are public by intent. Confirm each one should be.

## How to fix

### Supabase

Make the bucket private. Add policies so authenticated users can only read and write a prefix that includes their user id.

```sql
create policy "own folder read"
on storage.objects
for select
to authenticated
using (
  bucket_id = 'uploads'
  and (storage.foldername(name))[1] = auth.uid()::text
);
```

Write the matching insert and update policies with `with check`. Serve files to the owner with a short-lived signed URL created on the server after you check the session. Do not generate signed URLs for any path the caller passes. Check the prefix first.

### Firebase

```
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /uploads/{userId}/{file} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}
```

That sample is the shape, not a complete ruleset. Add size and content-type checks from [unsafe file uploads](/issues/unsafe-file-uploads). Do not deploy `if true` because a test upload failed.

### Vercel

If files live on Vercel Blob or another store, start from private access and issue signed reads. Check that product's docs for the exact flag. A file in `public/` on the Next.js app is always public. Do not put user uploads there.

### Next.js

The route that mints a signed URL has to authenticate the caller and authorize the path. A route that signs whatever `?path=` it receives is an IDOR. See [IDOR](/issues/idor).

## Agent prompt

```text
You are closing open file storage. Do not add features.

1. Find buckets, Blob stores, and public/ folders used for uploads.
2. Make user-file buckets private. Leave truly public marketing assets public and list them in your summary.
3. Add read and write rules so a user can only access their own path prefix.
4. Replace unlimited public URLs with short-lived signed URLs created only after a session check.
5. Do not sign a path just because the client sent it.

Show the policy and the signing code. Tell me how to confirm a logged-out request no longer loads another user's file.
```

## FAQ

### The bucket is private but getPublicUrl still works. How?

`getPublicUrl` builds a URL. It does not check the policy. If the bucket is actually private, that URL should fail when you open it logged out. If it loads, the object or the bucket is still public.

### Are signed URLs safe to put in an email?

They are a bearer token for that file until they expire. Use a short expiry. Do not use a year-long URL as a convenience.

## Sources

- [Supabase database linter, for the RLS distinction](https://supabase.com/docs/guides/database/database-linter)
- [Firebase security rules](https://firebase.google.com/docs/rules)
- [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html)
