Guides · VibeVigil
Is My Supabase Storage Bucket Public? How to Check and Make It Private
A plain-English guide to find out if the files in your Lovable, Bolt, v0 or Cursor app (photos, IDs, invoices, uploads) are open to anyone with the link, and how to lock them down with private buckets, storage policies and signed URLs.
If your app lets people upload files (profile photos, receipts, contracts, ID cards, homework, tax forms), those files probably live in Supabase Storage. Lovable, Bolt, v0 and Cursor often set this up for you in a few seconds.
The catch: AI tools often pick a public bucket because it "just works". Public is fine for logos and product photos. It is not fine for anything private.
This guide shows you how to check your buckets, and how to fix them. You don't need to be a developer.
The short answer
- Open your Supabase dashboard and go to Storage.
- Look at each bucket. If it says Public, anyone who has (or guesses) a file's link can open that file, with no login.
- If a public bucket holds private files, make it private, add storage policies so each user can only reach their own files, and show files with signed URLs that expire.
Why this matters
- Supabase says a public bucket lets anyone read its files without any access check, as long as they have the URL. Uploading, changing and deleting still follow your access rules. (Supabase: Storage buckets)
- A private bucket only serves files to requests that pass your storage policies (RLS on the
storage.objectstable), or through a signed URL. (Supabase: Storage access control) - Public file links follow a simple pattern, like
.../storage/v1/object/public/<bucket>/<file-name>. If file names are easy to guess (likeuser-12.pdf), a stranger can try many of them.
Step 1: List your buckets and what's in them
In the Supabase dashboard, open Storage and write down each bucket:
| Bucket | Public or private? | What's inside | Should it be public? |
|---|---|---|---|
avatars | Public | Profile photos | Maybe (your call) |
documents | Public | Invoices, IDs | No |
product-images | Public | Shop photos | Yes |
You can also run this in the SQL Editor:
select id, name, public
from storage.buckets
order by public desc, name;Any row where public is true is a public bucket.
A simple rule: if you would be upset to see a file posted online, it does not belong in a public bucket.
Step 2: Test it like a stranger
- Log in to your app and open a private file (for example, an uploaded receipt).
- Right-click the file or image and copy its address.
- Open a private / incognito window where you are not logged in.
- Paste the address.
If the file opens, anyone with that link can open it. If the link has /object/public/ in it, the bucket is public.
Also check how long the link works. A signed URL has a token= part and stops working after a set time. A public link works forever.
Step 3: Check your storage policies
Making a bucket private is only half the job. Your storage policies decide who can read, upload, change and delete files.
In the SQL Editor, run:
select policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'storage' and tablename = 'objects'
order by policyname;Look for these warning signs:
truein thequalorwith_checkcolumn on a private bucket. That lets everyone the policy covers through.- A policy for the
anonrole (people who are not logged in) on a private bucket. - A policy that only checks the bucket, like
bucket_id = 'documents', and nothing about who owns the file. That means any logged-in user can open every file in that bucket. Anyone can sign up, so "logged in" is not a real lock.
Step 4: Make the bucket private
In the dashboard: Storage, then the bucket's menu, then Edit bucket, and turn Public bucket off.
Or in the SQL Editor:
update storage.buckets
set public = false
where id = 'documents';Your app may stop showing these files right after this. That's expected. Steps 5 and 6 fix it.
Step 5: Add owner-only policies
A common, safe pattern is to put each user's files in a folder named with their user ID, like documents/<user-id>/receipt.pdf. Then you write policies that check that folder name.
Supabase has a helper, storage.foldername(), that splits a file path into folders. (Supabase: Storage helper functions)
-- Users can upload only into their own folder
create policy "Users upload own files"
on storage.objects for insert
to authenticated
with check (
bucket_id = 'documents'
and (storage.foldername(name))[1] = (select auth.uid())::text
);
-- Users can read only their own files
create policy "Users read own files"
on storage.objects for select
to authenticated
using (
bucket_id = 'documents'
and (storage.foldername(name))[1] = (select auth.uid())::text
);Add matching update and delete policies if users should be able to replace or remove their files.
Step 6: Show private files with signed URLs
For a private bucket, your app should ask Supabase for a signed URL: a link that works only for a short time. (Supabase: Serving assets)
const { data, error } = await supabase
.storage
.from('documents')
.createSignedUrl(`${user.id}/receipt.pdf`, 60) // works for 60 secondsPick a short time for sensitive files. If someone shares the link, it soon stops working.
Step 7: Clean up after the fix
- Old links still work only if the bucket is still public. Once it's private, the old
/object/public/links stop working. Good. - Rename files with guessable names if they held private data. Use random IDs instead of names like
invoice-1.pdf. - Check uploads too. Make sure a user can't upload into someone else's folder (the
insertpolicy in Step 5 handles this). - Re-test after every big AI change. Your coding agent can switch a bucket back to public or loosen a policy while fixing an unrelated error.
Copy-paste prompt for your coding agent
Paste this into Lovable, Bolt, v0 or Cursor:
Audit how this app uses Supabase Storage and make private files private.
1. List every storage bucket, whether it is public, and what kind of files it holds.
2. Keep a bucket public only if every file in it is meant for anyone to see (for example, logos or product photos). Make every other bucket private.
3. Store each user's files under a folder named with their user id: <bucket>/<user_id>/<file>.
4. Add RLS policies on storage.objects for select, insert, update and delete, so a user can only reach files where (storage.foldername(name))[1] = (select auth.uid())::text. No policy may use true, and the anon role must have no access to private buckets.
5. Replace every getPublicUrl call for private files with createSignedUrl and a short expiry (60 to 300 seconds).
6. Use random ids for new file names instead of guessable names.
7. Do not use the service_role or secret key in any browser code.
8. Show me the SQL migration and the list of changed files before applying.After the fix, repeat the stranger test from Step 2. The file should no longer open in a logged-out window.
FAQ
Is a public bucket always bad? No. Public is fine for files you want everyone to see, like a logo, blog images or product photos. It's a problem only for private files.
My bucket is private. Am I safe? Not always. Check your storage policies (Step 3). A policy with true, or one that lets any logged-in user read the whole bucket, still exposes files.
I made the bucket private and now images don't load. What happened? Your app was using public links. Switch those spots to signed URLs (Step 6), and make sure a select policy lets the owner read their own files (Step 5).
Does turning on RLS for my tables also protect my files? No. Table RLS protects rows in your database tables. Files are protected by the bucket setting and the policies on storage.objects. Check both. Our guide on checking Supabase RLS covers the tables.
Can someone list all the files in my bucket? That depends on your select policies on storage.objects. With owner-only policies like Step 5, a user can only see their own folder.
Related guides
- How to Check if Supabase RLS Is Enabled (and Actually Working)
- How to Check if Your Vibe-Coded App Is Leaking API Keys
Sources
- Supabase docs, Storage buckets (public vs private): https://supabase.com/docs/guides/storage/buckets/fundamentals
- Supabase docs, Storage access control: https://supabase.com/docs/guides/storage/security/access-control
- Supabase docs, Storage helper functions: https://supabase.com/docs/guides/storage/schema/helper-functions
- Supabase docs, Serving assets (signed URLs): https://supabase.com/docs/guides/storage/serving/downloads
Scan your app free. It checks the public page of a site you own. No account.