RLS Policies Referencing user_metadata in Supabase
Supabase security checks · HIGH
A policy trusts user_metadata, which users can edit themselves.
What goes wrong
In Supabase, user_metadata (stored as raw_user_meta_data in auth.users) can be changed by the signed-in user with supabase.auth.updateUser({ data: { role: 'admin' } }). An RLS policy that checks user_metadata to decide access can be bypassed by a user who edits their own metadata.
How it happens
The policy reads a role or flag from auth.jwt() -> 'user_metadata' instead of app_metadata.
How to find it
Search pg_policies for expressions that mention user_metadata or raw_user_meta_data.
SELECT tablename, policyname, cmd, qual, with_check
FROM pg_policies
WHERE schemaname = 'public'
AND (
qual::text LIKE '%user_metadata%'
OR qual::text LIKE '%raw_user_meta_data%'
OR with_check::text LIKE '%user_metadata%'
OR with_check::text LIKE '%raw_user_meta_data%'
);How to fix it
Use auth.jwt() -> 'app_metadata' in the policy instead. app_metadata can only be set server-side, with the service_role key or the Dashboard.
ALTER POLICY "<policy_name>" ON public.<table_name> USING ((SELECT auth.jwt() -> 'app_metadata' ->> 'role') = '<role>');
Check your own project
Locksoup runs these checks on your Supabase database in about a minute, with a read-only role, and gives you the SQL to fix what it finds.