PL-006high or mediumLab

SECURITY DEFINER function exposed

A function that runs with the owner's powers, callable by anyone through the API.

Severity
High or medium
Who can exploit it
Anyone who can call it
Found by
Free RLS check (static)
Lab proof
Call test included for your engineer

What it is

A security definer function runs with the rights of whoever created it, usually a role that ignores RLS. That is useful for small, careful helpers. It becomes a leak when two things combine:

  • Anyone can call it. Postgres lets every role execute a new function unless you revoke it, and Supabase exposes functions in public through the API at /rest/v1/rpc/<name>.
  • It returns or changes more than the caller should reach, such as looking up any user's email by id.

A definer function with no fixed search_path is a second problem: it can be tricked into running a look-alike object created in another schema. The free RLS check flags both (high when anonymous users can call it).

Example of vulnerable code

Simplified from the patterns we see in AI-built apps. Synthetic: not taken from any real app.

supabase/migrations/…_functions.sql (vulnerable)
create or replace function public.get_user_email(uid uuid)
returns text
language sql
security definer
as $$
  select email from auth.users where id = uid;
$$;
-- No revoke: anon can call it with any user's id.

Check it yourself

check-pl-006.sql
-- Definer functions in public: fixed search_path? callable by anon?
select p.proname as function_name,
       p.proconfig as settings,  -- look for search_path=...
       has_function_privilege('anon', p.oid, 'execute') as anon_can_call,
       has_function_privilege('authenticated', p.oid, 'execute') as signed_in_can_call
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public'
  and p.prosecdef;

The fix PatchLabs writes

PatchLabs narrows the function to the caller's own data, pins the search path, and grants it only to the roles that need it.

supabase/migrations/…_patchlabs_fix.sql
drop function public.get_user_email(uuid);
create or replace function public.get_my_email()
returns text
language sql
security definer
set search_path = ''
as $$
  select email from auth.users where id = (select auth.uid());
$$;
revoke execute on function public.get_my_email() from public, anon;
grant execute on function public.get_my_email() to authenticated;

With search_path = '', every name inside the function must be schema-qualified (auth.users, public.profiles). The report notes any frontend call that has to change from the old function name.

Every fix arrives as a migration file (plus any code change it needs, noted in the report) for your engineer to approve. Download it, or commit it on GitHub in one click from the report page. Automatic pull requests need our GitHub App, which is coming.

The proof it runs

call test · rpc
[BEFORE]  anon get_user_email(owner)
            → •••••@•••••         LEAK
[AFTER]   anon get_my_email()
            → permission denied     DENIED
          owner get_my_email()
            → their own email       OK
test SQL in the reportIllustrative output · fake data

The automatic three-identity proof covers table reads. For function findings the report includes the call test as SQL, run as the anonymous visitor and as the owner, for your engineer to run in the lab or a branch database.

call-test-pl-006.sql
begin;
set local role anon;
select public.get_my_email();  -- expect: permission denied for function get_my_email
rollback;

When the pattern is intended

Definer functions are the right tool in a few common places, and they are fine when the search path is pinned and the grants are narrow:

  • a helper used inside policies, such as has_role(uid, 'admin'), which returns only true or false about the caller and avoids recursive policies;
  • a trigger that creates a profile row when a user signs up (trigger functions can't be called through the API).

Supabase linter

0011 function_search_path_mutable
Flags functions without a fixed search_path.
0028 anon_security_definer_function_executable
Flags definer functions anonymous users can execute.
0029 authenticated_security_definer_function_executable
The same for signed-in users.

Free check

Does your app have PL-006?

Paste your public repo. We rebuild the database from your migrations, test it as a visitor, a stranger and the owner, and hand you the proven fix.

Free for your first appReads your public repo onlyOr run the free static RLS check

Last reviewed 10 Nov 2026. Finding IDs are stable: link to them freely. Spotted a mistake? Tell us.