Supabase Function Search Path Mutable warning: what it means and how to fix it
Short answer: the warning means one of your database functions doesn't say where to look for tables, so it borrows the caller's settings. Fix it with alter function public.my_function() set search_path = ''; and write every name in the body with its schema (public.profiles, not profiles). It matters most for security definer functions. To see what your own live site gives away, run a free SniffPup scan (SniffPup is a free passive security scanner for vibe-coded apps). Only check sites and projects you own.
Home › Guides › Function search path mutable · Last updated · By Ember, SniffPup
The fast way: a free SniffPup scan runs 15 checks from the outside, including Supabase read access using the public config your site already ships. No signup. Ember can sniff what strangers can reach, but she can't see inside your functions, so do the steps below too.
A 100 means no issues were found in our checks. It is never proof of security.
1. What the warning means
In Supabase's Security Advisor this shows up as Function Search Path Mutable, lint 0011_function_search_path_mutable, level WARN. The lint docs sum it up as "Unsecured function search path": without a fixed search path, the function "could behave unpredictably or be exploited to reference unintended database objects."
The search_path is the list of schemas Postgres checks when your code says profiles instead of public.profiles. A function with no search_path of its own uses the caller's. It's like telling a dog "fetch the ball" in a park full of balls: you'll get a ball. Maybe not yours.
2. Why it matters for security definer functions
Postgres runs a function either as the caller (security invoker, the default) or as its owner (security definer). Per Supabase's database functions docs, a function created in the Dashboard or by a migration is owned by postgres, which bypasses Row Level Security, and "when you use security definer, you must set the search_path."
The Postgres docs explain the trap: without a safe search_path, someone who can create objects in a schema the function searches can plant a table or function with the same name, and the definer function will happily use the fake one with its owner's powers.
One more thing from the Supabase docs: pinning the search_path doesn't change who can call the function. Any role can call it by default, including anon. If it shouldn't be public, also run revoke execute on function public.my_function() from public, anon;
3. The fix: set search_path = '' and schema-qualify names
Supabase recommends pinning the search path to an empty string. For an existing function, alter function does it without rewriting the body (include the argument types if it has any):
alter function public.my_function(uuid) set search_path = '';
Or put it in the definition next time you write the function:
create or replace function public.get_my_profile() returns setof public.profiles language sql security definer set search_path = '' as $$ select * from public.profiles where id = (select auth.uid()); $$;
The catch: with an empty search path, Postgres finds nothing by itself. Every table, view and function in the body needs its schema, like public.profiles or auth.uid(). Miss one and the function errors with "relation does not exist" the next time it runs, so test it right after.
4. Find every affected function
This is a simplified version of the query behind lint 0011. It lists functions with no search_path in proconfig, skipping Supabase's own schemas and functions that belong to extensions:
select n.nspname as schema,
p.proname as function,
pg_get_function_identity_arguments(p.oid) as args,
p.prosecdef as security_definer
from pg_catalog.pg_proc p
join pg_catalog.pg_namespace n on n.oid = p.pronamespace
left join pg_catalog.pg_depend d
on d.objid = p.oid and d.deptype = 'e' and d.classid = 'pg_catalog.pg_proc'::regclass
where n.nspname not in ('pg_catalog', 'information_schema', 'auth', 'storage', 'realtime',
'extensions', 'graphql', 'graphql_public', 'vault', 'pgsodium',
'supabase_functions', 'supabase_migrations', 'cron', 'net')
and d.objid is null
and not exists (
select 1 from unnest(coalesce(p.proconfig, '{}')) c where c like 'search_path=%'
)
order by p.prosecdef desc, 1, 2;Rows with security_definer = true float to the top. Fix those first. Then copy each row into alter function schema.function(args) set search_path = ''; and check its body for unqualified names.
5. Re-check: rerun the Security Advisor
Open Security Advisor in Supabase Studio and rerun it. Per the Advisors docs you can also run it with the CLI (supabase db advisors) or the MCP get_advisors tool. The 0011 warnings for the functions you pinned should be gone, and the query above should return no rows. Then call each function once from your app to make sure nothing broke. For views with the same "runs as owner" trap, see Supabase view bypasses RLS.
FAQ
What does the Supabase function_search_path_mutable warning mean?
It's Security Advisor lint 0011 (level WARN). It means a function in your database has no search_path of its own, so it uses the search_path of whoever calls it. Supabase's docs say that can make the function behave unpredictably, or be tricked into using tables or functions an attacker controls.
How do I fix it?
Pin the search path to an empty string, either with alter function public.my_function(arg types) set search_path = ''; or by adding set search_path = '' to the create or replace function statement. Then write every table and function in the body with its schema, like public.profiles, because an empty search path finds nothing on its own.
Is it dangerous or just tidy-up?
For a normal (security invoker) function it's mostly about consistent behavior. For a security definer function it matters a lot: that function runs with its owner's privileges, usually postgres, which bypasses RLS. Supabase's docs say that when you use security definer, you must set the search_path.
Does pinning search_path stop anon from calling my function?
No. Supabase's docs note that pinning the search_path doesn't change who can call the function. To control callers, revoke execute from public and anon and grant it only to the roles that need it.
Does a clean SniffPup report mean my functions are fine?
No. A score of 100 means no issues were found in our checks. It is never proof of security: an outside scan can't see your function settings, so use the SQL and the Security Advisor on this page too.
Sources: Supabase Advisors lint 0011 and Database functions docs, the lint 0011 SQL, and the Postgres CREATE FUNCTION and ALTER FUNCTION docs. Checked October 30, 2026.
A score of 100 means no issues were found in our checks. It is not proof that your app is secure.