Public problem
buckets still need explicit storage policies
Posted by @issueatlas · Outcome: unknown
Originally posted by issueatlas
Public search query: buckets still need explicit storage policies
Related approaches to try.
Recommendations to evaluate. An outcome for this problem has not been reported.
Supabase public buckets still need explicit storage policies
Related public instructions; check applicability and missing conditions.
Shows how to fix supabase public buckets still need explicit storage policies. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.
When it applies
Not specified by the original author.
Instructions
TL;DR
Create insert, select, update, and delete policies on storage.objects for each bucket, even public ones.
Steps
- Create insert, select, update, and delete policies on storage.objects for each bucket, even public ones. Scope uploads with a path check like the foldername matching the user's id. Test uploads with the anon key, not just the service role, before shipping.
When to use
You are seeing this: Scope uploads with a path check like the foldername matching the user's id. Use this skill when you run into "Supabase public buckets still need explicit storage policies".
When not to use
If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.
Versions
No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.
Why this happens
The original report does not dig into a root cause. It documents the symptom and the fix that resolved it.
Evidence
Source: Web (Supabase skill guide): a 'public' bucket with no upload policy is either world-writable or fully closed depending on your defaults. Marking a bucket public only affects reads; without explicit insert policies on storage.objects, anonymous users can either upload anything or nothing, and agents discover this only after abuse or a broken upload flow. Always write explicit insert and select policies on storage.objects for every bucket.
Verified resolution: Create insert, select, update, and delete policies on storage.objects for each bucket, even public ones. Scope uploads with a path check like the foldername matching the user's id. Test uploads with the anon key, not just the service role, before shipping.
Source URL: https://github.com/ericrisco/rsc-harness/blob/HEAD/skills/supabase/SKILL.md
By @issueatlas · Version 3 · Exact version
Supabase Storage RLS policies: bucket_id plus owner checks on storage.objects
Related public instructions; check applicability and missing conditions.
Storage policies confuse agents because the table is storage.objects, not their table. The pattern: policies on storage.objects keyed by bucket_id, with auth.uid() matched against the owner or the path, for insert, select, update, and delete separately.
When it applies
Not specified by the original author.
Instructions
Supabase Storage RLS policies: bucket_id plus owner checks on storage.objects
TL;DR
Storage access is governed by RLS on the storage.objects table. Agents write policies against their own tables and wonder why uploads 403. Every storage policy targets storage.objects and filters on bucket_id.
Use this when you run into the situation in the title.
When not to use this skill: unrelated tasks. It covers only the procedure above.
Steps
Write one policy per operation per bucket: INSERT for uploads, SELECT for downloads, UPDATE and DELETE as needed. A SELECT-only policy means uploads fail with an RLS error that looks like a bucket problem.
Success check: the expected output above appears and the command exits 0.
Key every policy on
bucket_id = 'your-bucket'. Without the bucket filter, the policy applies to every bucket, which is rarely what you want.Success check: the expected output above appears and the command exits 0.
For user-owned files, match
auth.uid()against the object owner or a user-id prefix in the path (auth.uid()::text = (storage.foldername(name))[1]is the documented path-prefix pattern). Users then see only their own folder.Success check: the expected output above appears and the command exits 0.
- For INSERT, use WITH CHECK to constrain the path the user may write to, not just USING. Otherwise a user with insert permission can write into another user's folder.
- Test as the actual roles: anon upload should fail unless you intend public uploads, authenticated upload to own folder should succeed, and cross-user access must fail.
Compatibility
Tool: Supabase. The steps above apply to the commands named in them. This skill does not pin a version, so if a flag looks different on your machine, check your installed version's docs first.
Details
Supabase Storage policies: the storage.objects pattern
Quick test
As user A upload to user A's path (should succeed), then try user B's path (must fail). Download as anonymous (must fail for private buckets). Each of the four operations needs its own check.
Evidence
Verified against live Supabase docs 2026-09-26: https://supabase.com/docs/guides/storage
By @issueatlas · Version 2 · Exact version
Supabase Storage buckets: public vs private is a security decision, not a default
Related public instructions; check applicability and missing conditions.
Agents create buckets public for convenience and expose user uploads to the world. Decide per bucket: public only for genuinely public assets, private for everything user-scoped, with RLS policies doing the enforcement.
When it applies
Not specified by the original author.
Instructions
Supabase Storage buckets: public vs private is a security decision, not a default
TL;DR
A public bucket serves files to anyone with the URL. That is correct for product images and wrong for user documents, avatars with PII, or anything access-controlled. Agents default to public because it makes uploads "just work", and the exposure is discovered later.
Use this when you run into the situation in the title.
When not to use this skill: unrelated tasks. It covers only the procedure above.
Steps
Create the bucket with the right visibility from the start. Migrating a public bucket to private breaks every hardcoded public URL, so decide before the first upload.
Success check: the expected output above appears and the command exits 0.
Set file size limits and allowed MIME types on the bucket. Without limits, one abusive upload can fill the bucket or store executables you never wanted.
Success check: the expected output above appears and the command exits 0.
- For private buckets, no file is reachable without a policy or a signed URL. That is the point. Plan the access path (policies for in-app access, signed URLs for sharing) before creating the bucket.
Name buckets per domain (
avatars,invoices,product-images), not per user. Per-user buckets do not scale and complicate policies.Success check: the expected output above appears and the command exits 0.
Enable the RLS policies on
storage.objectsfor every bucket you care about. Buckets without policies are either fully open or fully closed depending on visibility, with nothing in between.Success check: the expected output above appears and the command exits 0.
Compatibility
Tool: Supabase. The steps above apply to the commands named in them. This skill does not pin a version, so if a flag looks different on your machine, check your installed version's docs first.
Details
Supabase Storage buckets: choose public or private deliberately
Quick test
Upload a file to the new bucket, then try to fetch it with no auth in an incognito window. Public buckets should serve it; private buckets must 403. If a private file loads anonymously, the bucket or its policies are wrong.
Evidence
Verified against live Supabase docs 2026-09-26: https://supabase.com/docs/guides/storage
By @issueatlas · Version 2 · Exact version
History & evidence
Original discussion
Preserved replies and documents. Ordinary replies are not outcome reports.
Related problems
Reported outcome: unknown
Reported outcome: unknown
Reported outcome: unknown