Blog · October 20, 2026 · 7 min read

Is your Supabase file storage public? How to check your buckets

AI tools often make Supabase storage buckets public so uploads just work. Here is how to check whether your users' files are open to anyone, and how to lock them down.

You added an upload button to your app. Users can add profile photos, invoices, ID scans or PDFs, and it all works. But where do those files live, and who can open them?

If you built with Lovable, Bolt, Cursor or Replit on Supabase, your files probably sit in a Supabase Storage bucket. A bucket is either public or private, and one wrong click or one line from your AI tool decides whether anyone with a link can read every file. You can't see this by clicking around your app, because it works the same either way.

This guide shows how to check in about five minutes and how to fix it.

Why this matters

Files are the part of an app people forget about. You probably thought hard about the database, but a folder of passports, contracts or screenshots is just as private.

Access control is also the most common serious web weakness there is. In the OWASP Top 10 (2021), Broken Access Control is number one. OWASP's data shows that 94% of the applications tested had some form of it, and the category had over 318,000 recorded occurrences, more than any other category in that dataset. A public bucket that should be private is a textbook case: "access should only be granted for particular capabilities, roles, or users, but is available to anyone."

How buckets work (the short version)

According to the Supabase Storage docs, there are two kinds of bucket:

Public buckets are fine for things everyone is meant to see: logos, blog images, public product photos. They are the wrong choice for anything that belongs to one user.

A public URL also looks the same for every file in the bucket. Only the file name differs, and file names are easy to guess: invoice-1.pdf, invoice-2.pdf, or a user ID you can see elsewhere in your app.

The mistakes we see

  1. A private-looking bucket that is set to public. The AI tool (or you, to fix a broken image) switched it on so previews would load.
  2. One bucket for everything. Public logos and private documents live in the same public bucket.
  3. Policies that say true. Policies on storage.objects such as using (true) let any visitor read, or even upload, files. The Supabase access control docs show with check (true) only as a starting point to tighten, not something to ship.
  4. Guessable paths. Files named documents/1.pdf instead of <user-id>/<random-name>.pdf.
  5. The service key in the browser. The docs say the service key "entirely bypass[es] RLS policies, granting you unrestricted access to all Storage APIs" and must not be shared publicly. See our guide to keys in your JavaScript.

Step by step: check your buckets

1. See which buckets are public

In the Supabase dashboard, open Storage. Public buckets are marked as such. Or open the SQL Editor and run:

select id, name, public
from storage.buckets
order by public desc;

Every row with public = true is readable by anyone who has a file link. For each one, ask: "Would I be happy if a stranger read everything in here?"

2. Look at your storage policies

Still in the SQL Editor:

select policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'storage' and tablename = 'objects';

Red flags: qual or with_check that is just true, a policy with no bucket_id = '...' limit, or a policy for the anon role on a bucket with private files. If you have no policies at all, uploads from your app should fail, because Storage does not allow uploads without RLS policies. If uploads work and you never wrote a policy, find out what created one.

3. Test it like a stranger

Open a private window where you are not logged in. Take the link of a file that one of your users uploaded. It looks like this:

https://YOUR-PROJECT.supabase.co/storage/v1/object/public/BUCKET/path/to/file.pdf

If it opens, anyone can open it. Now try changing the file name or the user ID in the path to another real one. If that also opens, the whole bucket is exposed.

You can also test listing. Replace YOUR-PROJECT, YOUR_ANON_KEY and BUCKET, and run:

curl -s -X POST "https://YOUR-PROJECT.supabase.co/storage/v1/object/list/BUCKET" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prefix":"","limit":5}'

The anon key is the public one that already ships in your app, so it is safe to use here. If you get back a list of file names, strangers can see what is stored. An empty list [] or an error means listing is blocked. (Reading a file when you know its exact public URL can still work, which is why step 1 matters.)

4. Check the two-user rule for files

Sign up as user A and upload a file. Sign up as user B in another browser. Can B open A's file, by link or in the app? This is the same test we describe in the pre-launch checklist, applied to files.

How to fix it

  1. Make the bucket private. In Storage, open the bucket's settings and turn off Public bucket. Do this for every bucket that holds user data.
  2. Add a policy so each user only reaches their own folder. Files go in a folder named after the user's ID. Policy based on the pattern in the Supabase docs:
create policy "Users read their own files"
on storage.objects for select
to authenticated
using (
  bucket_id = 'documents'
  and (storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);

create policy "Users upload to their own folder"
on storage.objects for insert
to authenticated
with check (
  bucket_id = 'documents'
  and (storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);

Change documents to your bucket name. Add update and delete policies in the same way if users may replace or remove files.

  1. Serve private files with signed URLs. Your app should ask Supabase for a link that expires, using createSignedUrl, instead of storing a permanent public link. The docs describe this as one of the only two ways to download from a private bucket.
  2. Split your buckets. Keep one public bucket for things like logos and one private bucket for everything else.
  3. Replace stored links. If your database saved the old public URLs, update your app to save the file path and generate a signed link when needed. Old public links will stop working once the bucket is private, which is what you want.
  4. Assume exposed files were seen. If sensitive files were public for a while, consider telling affected users and moving the files to a new private bucket with random names.

You can paste this into your AI tool: "List every Supabase Storage bucket this app uses. Make buckets with user data private, store files under a folder named after the user's ID, add Row Level Security policies on storage.objects so users can only read and write their own folder, and use createSignedUrl instead of public URLs. Never use the service_role key in client code."

Related checks

Files are only one of the places data can leak. If you haven't yet, also check whether your database tables are open and read Supabase RLS in plain English.

Check it for free

Greenlight Scan looks at your live app the way a stranger would, and gives you a traffic-light verdict in about a minute. Run the free scan to see if your storage or database is open. If you want a deeper check with step-by-step fixes, the $19 Full Scan goes through everything in detail.

Newsletter

The Friday leak report

One short email a week: the newest ways AI-built apps leak data, and the one-line fix for each. No spam, unsubscribe any time.