Marked as helpful by the asker
Your instinct is right. Never ship the service key to a browser, it bypasses every rule you have.
The standard setup:
- Bucket public for reading (avatars are public anyway), private for writing.
- A storage policy that lets a user write only into their own folder:
create policy "own avatar" on storage.objects for insert
with check (bucket_id = 'avatars' and (storage.foldername(name))[1] = auth.uid()::text);- Upload from the browser with the anon key and the user's session. Limit file size and type in the same policy or in the client.
Delete the service key from your frontend code today and rotate it in the dashboard, it has been in your bundle.