---
title: "Supabase Function Search Path Mutable: cosa significa"
description: "Cosa significa l’avviso function_search_path_mutable, chi può sfruttarlo, perché search_path = public non bastava nel mio test e la correzione."
url: "https://madsalt.tech/it/notes/supabase-function-search-path-mutable/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/it/about/"
language: "it"
published: 2026-11-01
---

# Supabase Function Search Path Mutable: cosa significa

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](https://github.com/bdougie/contributor.info/commit/3c73dda01df80faccb75278ec0f826a84e01a6d9).

## 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](https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable) 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.](https://madsalt.tech/charts/it/supabase-function-search-path-mutable.svg)

*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](https://github.com/supabase/splinter/issues/100) 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](https://www.postgresql.org/docs/current/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY). 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:

```sql
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:

```sql
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](https://github.com/supabase/supabase/issues/33131). 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

- Nell'Advisor corregga per prime le funzioni `SECURITY DEFINER` dell'elenco; le funzioni invoker girano comunque con i diritti di chi le chiama.
- Il [controllo di sicurezza Supabase](https://madsalt.tech/it/tools/supabase-security-check/) gratuito mostra i risultati dell'Advisor accanto alle proprie regole, comprese le funzioni definer richiamabili tramite l'API e quelle che controllano `current_user`.
- Cosa possono saltare le funzioni definer è in [NULL in IF NOT di PL/pgSQL: controllo saltato su 3683 task](https://madsalt.tech/it/notes/plpgsql-if-not-null-fails-open/), e le policy che chiamano simili helper sono in [Esempi di Supabase RLS policy, testati tramite API](https://madsalt.tech/it/blog/supabase-rls-policy-examples/).
