← Problem log

Supabase Storage upload by a restricted user: 403 "new row violates row-level security policy"

Last verified Oct 22
Exact text to search:
new row violates row-level security policy

1What happened

A scheduled job uploads 3 files to a Supabase Storage bucket every 15 minutes, overwriting the old ones. It used to upload with the service key. We moved it to a restricted user, so it could only write those 3 files. The user signed in fine (200), but every upload got 403 "new row violates row-level security policy". The page the files feed went stale.

It was down almost 4 days in all. First the job lost its old key, because we switched that key off without checking that this job still read it (Oct 18 to Oct 21). Then the 403 (Oct 21 to Oct 22).

What didn't help:

  1. Looking for a leftover code fix that never shipped: it had already shipped.

  2. Checking the sign-in: it returned 200, so the login was fine.

The cause: the storage policies were written but never applied to the database.

2What we did

  1. Applied 3 policies on storage.objects. The job overwrites files (an upsert, the x-upsert header), so it needs SELECT and UPDATE as well as INSERT.

    create policy "uploader insert" on storage.objects for insert to authenticated
      with check (bucket_id = '<bucket>' and name in ('<file1>','<file2>','<file3>')
                  and auth.uid() in ('<uploader-user-id>'));
    create policy "uploader select" on storage.objects for select to authenticated
      using (bucket_id = '<bucket>' and name in ('<file1>','<file2>','<file3>')
             and auth.uid() in ('<uploader-user-id>'));
    create policy "uploader update" on storage.objects for update to authenticated
      using (bucket_id = '<bucket>' and name in ('<file1>','<file2>','<file3>')
             and auth.uid() in ('<uploader-user-id>'))
      with check (bucket_id = '<bucket>' and name in ('<file1>','<file2>','<file3>')
                  and auth.uid() in ('<uploader-user-id>'));

    Choices that matter:

    1. Pin the user by its id, not its email.

    2. Allow exact file names only. If the bucket is public, a wider rule lets the user write anything anyone can read.

    3. No DELETE policy.

    4. No owner check. The existing files were written by the service key and have no owner, so an owner check refuses the first overwrite.

3How we know it worked

On Thu Oct 22, after the policies went in, a test upload returned 200. Every run in the job's log from 7:15 to 8:30 AM PT (6 runs, every 15 minutes) logged all 3 files uploaded. The same user writing a file name outside the list still got 403, so it can't write anything else.

4What you can take from it

If a Storage upload gets this 403 and the sign-in works, read the policies on storage.objects. An overwrite needs INSERT, SELECT and UPDATE.

supabasestoragerlspermissionsSolved Oct 22
Robb Lejuwaan

Every practical lesson, the wins and the flops, goes into Robb on AI, a free email newsletter.

Robb Lejuwaan, writer of Robb on AI
Read it free