Files that users upload, such as avatars, invoices, ID documents and exports, live in a bucket rather than in your database. Each bucket is either public or private, and private buckets rely on policies to decide who gets in. In Supabase Storage those policies are row level security rules on the storage.objects table, and the documentation states that by default no uploads are allowed until you add one.
The frequent mistake in AI-built apps is making a bucket public so that images display without extra code. Supabase's documentation is direct about what that means: a public bucket bypasses access controls for retrieving files, so anyone who has the URL can fetch the file. That is fine for a logo and wrong for a passport scan. A second mistake is a policy that lets any signed-in user read or overwrite every file, instead of only files in their own folder.
Keep one public bucket for assets that are truly public and a private one for everything else. Write policies that tie each file path to the user who owns it, serve private files through short-lived signed URLs, restrict upload size and file type, and on S3 leave Block Public Access switched on unless you have a specific reason.
create policy "Users read own files"
on storage.objects for select
to authenticated
using (
bucket_id = 'documents'
and (storage.foldername(name))[1] = auth.uid()::text
);