Vai al contenuto

Supabase Function Search Path Mutable: cosa significa

Supabase

Visualizza come MarkdownApri in ClaudeApri in ChatGPTApri in Perplexity

Anche la domanda viene copiata. Se la chat si apre vuota, la incolli.

In breve

L’avviso significa che una funzione non ha un search_path fisso; ogni nome non qualificato al suo interno, come admins, viene risolto come dice la sessione di chi la chiama. Nel mio test locale una funzione SECURITY DEFINER is_admin() ha restituito true per un non admin dopo che quell’utente aveva creato una tabella temporanea chiamata admins, e fissare search_path = public non l’ha impedito. Ha funzionato: set search_path = '' con ogni tabella scritta come public.admins.

Il Security Advisor di Supabase elenca questo come lint 0011, “Function Search Path Mutable”, livello WARN, con il dettaglio “Function public.x has a role mutable search_path”. Segnala ogni funzione senza l’impostazione, comprese quelle SECURITY INVOKER, quindi in un progetto con molti helper l’elenco si allunga: un progetto open source ne ha corrette 99 in una sola migrazione.

Cosa significa “role mutable search_path”?

search_path è l’elenco degli schemi in cui Postgres cerca un nome senza schema davanti. Una funzione senza impostazione propria usa l’elenco di chi la chiama. La pagina del lint di Supabase consiglia di fissarlo a una stringa vuota, il che obbliga ogni riferimento nella funzione a essere completamente qualificato.

Per una funzione SECURITY DEFINER conta di più: gira con i diritti del suo proprietario, di solito postgres, che aggira la RLS. Se chi chiama può decidere quale admins legge, decide un controllo che gira come postgres.

Si può sfruttare nel mio test?

Su un Postgres 17.10 locale ho creato una tabella public.admins con un admin e quattro versioni della stessa funzione, select exists (select 1 from admins where user_id = uid), tutte SECURITY DEFINER. Poi mi sono collegato con un ruolo a bassi privilegi che può eseguire SQL e ha il diritto predefinito di creare tabelle temporanee, e ho creato pg_temp.admins contenente l’utente A, che non è admin:

is_admin(A) dopo una tabella temp admins. Funzione SECURITY DEFINER, Postgres 17.10 locale. Nessun search_path: pg_temp.admins → admins → temp → true; search_path = public: pg_temp.admins → admins → temp → true; search_path = '' + public.admins: pg_temp.admins → public.admins → false.
La mia riproduzione su un Postgres locale, 24 ottobre 2026.
Impostazione della funzione Condizione del lint is_admin(A) dopo la tabella temporanea
nessuna segnalata true
set search_path = public passa true
set search_path = '', il corpo usa public.admins passa false
set search_path = public, pg_temp passa false

La seconda riga è quella che mi ha sorpreso. Un maintainer di Supabase ha scritto nel 2024 che un percorso fisso non vuoto non comporta rischi per la sicurezza, e il lint lo accetta. Ma Postgres cerca prima nello schema temporaneo, a meno che pg_temp non sia nominato nel percorso, come avverte la sua documentazione sulla scrittura di funzioni SECURITY DEFINER. Quindi con search_path = public una tabella temporanea può ancora oscurare un nome non qualificato.

Chi può davvero farlo su Supabase?

Non un visitatore tramite la sua app. La Data API non può creare tabelle temporanee né cambiare il search_path della sessione, quindi la sola publishable key non arriva fin lì. Serve una connessione diretta al database: un secondo servizio con un proprio login, uno strumento di BI o di reportistica, il ruolo di sola lettura di un collaboratore esterno, una stringa di connessione trapelata. Nella maggior parte delle piccole app il rischio è basso; in ogni progetto in cui persone o strumenti diversi da Lei hanno login al database, corregga prima le funzioni definer.

Come si risolve?

Ricrei la funzione con percorso vuoto e nomi di schema nel corpo:

create or replace function public.is_admin(uid uuid)
returns boolean
language sql stable security definer
set search_path = ''
as $$ select exists (select 1 from public.admins where user_id = uid) $$;

revoke execute on function public.is_admin(uuid) from public, anon;

Il revoke conta quanto il percorso: una funzione definer in public è richiamabile tramite la Data API; senza di esso qualsiasi ospite può chiedere a /rpc/is_admin se un dato utente è admin. Lo restituisca solo ai ruoli che ne hanno bisogno, oppure tenga helper come questo in uno schema che l’API non espone.

Non esegua soltanto alter function public.is_admin(uuid) set search_path = ''. Mette a tacere l’Advisor e rompe la funzione: nella mia prova la chiamata successiva è fallita con 42P01: relation "admins" does not exist. Anche gli operatori delle estensioni hanno bisogno del loro schema, come operator(extensions.<=>) per pgvector.

Per elencare cosa il lint segnalerà in public, con le funzioni definer per prime:

select p.proname, p.prosecdef as security_definer
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public'
  and not exists (select 1 from unnest(coalesce(p.proconfig, '{}')) c
                  where c like 'search_path=%')
order by p.prosecdef desc, p.proname;

La correzione ha un costo?

A volte. Qualsiasi clausola set impedisce a Postgres di incorporare una semplice funzione SQL nella query che la chiama, come segnalato su GitHub. Ho misurato entrambe le forme su 200.000 righe con un indice sulla colonna filtrata (mediana di 7 esecuzioni):

Funzione SQL, poi filtrata per proprietario Senza set set search_path = ''
Filtro fuori: visible_items() where owner = ... 0,053 ms, indice usato 36,7 ms, scansione completa
Filtro dentro: items_of(owner) 0,056 ms 0,082 ms

Se una funzione SQL che restituisce un insieme viene interrogata con un filtro fuori, sposti il filtro dentro come parametro prima di fissare il percorso.

Controlli il suo progetto

© 2026 Denys Zaichenko. Articolo originale: https://madsalt.tech/it/notes/supabase-function-search-path-mutable/