Home / Guides

Supabase SECURITY DEFINER functions: the risk and the fix

Updated 2026-10-14 ยท Defensive guide for your own app

Short answer

A SECURITY DEFINER function runs with its owner's privileges, which on Supabase is usually a role that bypasses RLS. By default, functions in public are executable by anon and authenticated, so anyone with your public key can call them at /rest/v1/rpc/<name>. Supabase flags this as lints 0028 and 0029. The fix: revoke EXECUTE, switch to SECURITY INVOKER, or move the function to a schema the API doesn't expose.

Find them

select p.proname,
       pg_get_function_identity_arguments(p.oid) as args,
       pg_catalog.pg_get_userbyid(p.proowner) as owner,
       has_function_privilege('anon', p.oid, 'execute') as anon_can_call
from pg_catalog.pg_proc p
join pg_catalog.pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.prosecdef
order by p.proname;

The fix

Pick one per function:

-- A. Nobody outside the server should call it
revoke execute on function public.my_priv_op(int, text) from anon, authenticated, public;

-- B. It doesn't actually need elevated rights
alter function public.my_priv_op(int, text) security invoker;

-- C. Stop future functions being auto-executable
alter default privileges in schema public
  revoke execute on functions from anon, authenticated, public;

If a function must stay definer (for example, a helper used inside a policy), keep it in a non-exposed schema such as private, pin set search_path = '', fully qualify every name, and grant execute only to the role that needs it.

create function private.has_role(r text) returns boolean
language sql security definer set search_path = '' stable as $$
  select exists (select 1 from public.user_roles
                 where user_id = (select auth.uid()) and role = r);
$$;
revoke execute on function private.has_role(text) from public;
grant usage on schema private to authenticated;
grant execute on function private.has_role(text) to authenticated;

How to check

Check your own project in seconds

Upload a .zip of your own Lovable, Bolt, Cursor, Supabase or Firebase project. ProdCanary reviews the code statically and shows your top 3 risks with plain-English fixes. Your upload is deleted right after the scan.

Run a free scan

FAQ

Why does revoking from anon alone not fix it?

Postgres grants EXECUTE to PUBLIC by default and Supabase also grants authenticated. Revoke from anon, authenticated and public together, as Supabase's lint docs recommend.

Are views affected too?

Yes. Views default to security definer behaviour and bypass RLS. Use with (security_invoker = true) on Postgres 15+ (lint 0010).

Is every security definer function a bug?

No. It's a real risk when the function is in an exposed schema and callable by anon or authenticated without validating input.

Sources

This guide is for securing apps you own or are authorised to work on. Facts above are from the official docs linked; check them for changes.