Pin search_path in definer functions; keep extensions out of public

An extension installed into public exposes its functions as RPCs. Install into extensions or one schema per extension; set search_path empty in definer code.

Security

· Chapter

32

·

2

min read

The answer. search_path is the list of schemas Postgres consults for an unqualified name, and it is the difference between "call my helper" and "call whatever the caller managed to create with the same name". Two rules cover it. For every security definer function set search_path = '' in the definition and qualify every table and function (public.tickets, extensions.uuid_generate_v4()); the advisor calls the missing setting function_search_path_mutable (0011). For extensions, never install into public: every function an extension ships would become an RPC. Supabase's convention is the extensions schema, which is already on the role's search path and on PostgREST's extra_search_path; a schema per extension is tidier but must be added to both lists, because extensions call their own functions unqualified. pg_cron is the exception that only lives in pg_catalog, and db diff does not capture it.

The pattern.

create extension if not exists vector with schema extensions;
create extension if not exists pg_cron;                     -- pg_catalog only; write this line by hand in a migration
alter role postgres set search_path = "$user", public, extensions;   -- role default; extra_search_path mirrors it for PostgREST

create or replace function public.next_ticket_number(t text) returns bigint
language sql security definer set search_path = '' as $$
  select coalesce(max(number), 0) + 1 from public.tickets where tenant = t
$$;
Dashboard → Settings → API → "Extra search path": public, extensions   (local: [api] extra_search_path)

Watch out.

  • show search_path; in the SQL editor shows the postgres role's path, not PostgREST's; the two are configured separately.
  • Extensions installed with the dashboard toggle are captured by db diff (except pg_catalog ones); commit the migration so a fresh branch has them.
  • An extension moved between schemas changes function signatures for anyone who qualified the old name; plan it like a rename.

Related: security-definer-rpc-is-a-controlled-hole-in-rls · pg-cron-inside-postgres-vs-a-worker-cron-outside · pgvector-column-hnsw-index-match-function-rpc