Replies: 6 comments
|
This is a DELETE command type mismatch — the policy likely has Check (run in SQL Editor): SELECT policyname, cmd, roles, qual, with_check
FROM pg_policies WHERE tablename = 'your_table';Fix: Recreate with DROP POLICY IF EXISTS "your_policy" ON your_table;
CREATE POLICY "owner_all" ON your_table
FOR ALL TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);Why SQL works but API fails: Free audit script: https://github.com/cekuu35/supabase-rls-leak-demo |
|
This is a DELETE command type mismatch — the policy likely has Check (run in SQL Editor): SELECT policyname, cmd, roles, qual, with_check
FROM pg_policies WHERE tablename = 'your_table';Fix: Recreate with DROP POLICY IF EXISTS "your_policy" ON your_table;
CREATE POLICY "owner_all" ON your_table
FOR ALL TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);Why SQL works but API fails: Free audit script: https://github.com/cekuu35/supabase-rls-leak-demo |
|
PostgreSQL’s
and:
So having a valid |
|
One thing worth flagging before chasing the SELECT-policy angle: testing as superuser doesn't tell you much here — superuser bypasses RLS entirely, so "it works as superuser" just confirms the row exists and your filter matches, not that your policy's To isolate this properly, use Supabase Studio's SQL Editor role impersonation (supabase.com/features/user-impersonation) and run the delete directly as that specific user: DELETE FROM public.push_tokens
WHERE user_id = auth.uid()
returning *;That tells you definitively whether the policy itself is the problem, independent of anything SELECT policyname, cmd, roles, qual, with_check
FROM pg_policies
WHERE tablename = 'push_tokens’;If you only see the one Fix: add a CREATE POLICY "can view own push token"
ON public.push_tokens
for SELECT
to authenticated
using (user_id = auth.uid());Then re-run your original delete, it should now report the correct row count. |
|
Your With Supabase RLS, a filtered Try adding: create policy "can view own push token"
on public.push_tokens
for select
to authenticated
using (auth.uid() = user_id);Keep your existing So basically: SELECT policy = lets Supabase find the row |

Uh oh!
There was an error while loading. Please reload this page.
Project ref: qotymdmeqfbwminykkxu
Postgres version: PostgreSQL 17.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 15.2.0, 64-bit
Table:
public.push_tokens(columns:id uuid,user_id uuid not null,token text not null unique,created_at/updated_at timestamptz)RLS: enabled, single policy:
Repro (via PostgREST, anon key, real anonymous auth session — reproduced with a freshly created auth user each time):
sub = <uid>,role = authenticated.auth.uid()internally (confirmsuser_idis stored correctly).DELETE FROM push_tokens WHERE user_id = <uid>as theauthenticatedrole — reproduced three independent ways:.delete().eq('user_id', uid).count(exact)SET LOCAL ROLE authenticated; SET LOCAL request.jwt.claims to the same sub/role; DELETE ...; GET DIAGNOSTICS row_countuser_id, run as a superuser (RLS bypassed), succeeds immediately and removes the matching row(s).tokeninstead ofuser_id) under the sameauthenticatedRLS context produces the identical silent 0-rows-affected result — this doesn't appear to be specific to theuser_idcolumn or its policy expression.Already ruled out:
auth.uid()resolves to the correct value in the same session/transaction where the DELETE fails (confirmed viaRAISE NOTICE/SELECT auth.uid()immediately before the DELETE, and via a single atomicDO $$ ... $$block to eliminate any statement-boundary/session ambiguity).roleclaim is genuinelyauthenticated, notanon(checked directly, decoded from the actualAuthorizationheader sent in the HTTP request).refreshSession()before the delete made no difference.pg_policies; dropped and recreated identically — no change in behavior.user_idcolumn type is plainuuid, no cast/whitespace mismatch (matches_explicit_cast=truechecked explicitly against every candidate row).pg_triggerchecked, empty).GET DIAGNOSTICS ... row_countused as the affected-row measure (independent ofRETURNING/SELECT-policy visibility, since this table intentionally has no SELECT policy — ruling out a RETURNING-visibility red herring).Workaround in place: routing the delete through a
SECURITY DEFINERfunction that resolvesauth.uid()internally and bypasses RLS entirely, mirroring the existingregister_push_token()pattern already used for INSERT in this project. This works reliably and has been verified independently against the real project multiple times.Filing this because the underlying RLS DELETE-under-
authenticated-role behavior itself appears broken for this project/table, not because we lack a workaround. Happy to provide any additional diagnostic queries if useful.All reactions