---
title: "Supabase Function Search Path Mutable : ce que ça signifie"
description: "Ce que signifie l’alerte function_search_path_mutable, qui peut l’exploiter, pourquoi search_path = public ne suffisait pas dans mon test, et la correction."
url: "https://madsalt.tech/fr/notes/supabase-function-search-path-mutable/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/fr/about/"
language: "fr"
published: 2026-11-01
---

# Supabase Function Search Path Mutable : ce que ça signifie

En bref

L’alerte signifie qu’une fonction n’a pas de `search_path` fixe ; chaque nom non qualifié qu’elle contient, comme `admins`, est résolu comme le dit la session de l’appelant. Dans mon test local, une fonction `SECURITY DEFINER` `is_admin()` a renvoyé `true` pour un non-admin après que cet utilisateur a créé une table temporaire nommée `admins`, et fixer `search_path = public` ne l’a pas empêché. Ce qui a marché : `set search_path = ''` avec chaque table écrite `public.admins`.

Le Security Advisor de Supabase liste cela comme le lint 0011, « Function Search Path Mutable », niveau WARN, avec le détail « Function `public.x` has a role mutable search_path ». Il signale chaque fonction sans ce réglage, y compris les fonctions `SECURITY INVOKER` ; sur un projet qui a beaucoup d’aides, la liste devient longue : un projet open source en a [corrigé 99 dans une seule migration](https://github.com/bdougie/contributor.info/commit/3c73dda01df80faccb75278ec0f826a84e01a6d9).

## Que signifie « role mutable search_path » ?

`search_path` est la liste des schémas dans lesquels Postgres cherche un nom sans schéma devant lui. Une fonction sans réglage propre utilise la liste de l’appelant. La [page du lint](https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable) de Supabase recommande de le fixer à une chaîne vide, ce qui oblige chaque référence dans la fonction à être entièrement qualifiée.

Pour une fonction `SECURITY DEFINER`, c’est le plus important : elle s’exécute avec les droits de son propriétaire, généralement `postgres`, qui contourne la RLS. Si l’appelant peut orienter quel `admins` elle lit, il oriente un contrôle exécuté en tant que `postgres`.

## Peut-on l’exploiter dans mon test ?

Sur un Postgres 17.10 local, j’ai créé une table `public.admins` avec un admin et quatre versions de la même fonction, `select exists (select 1 from admins where user_id = uid)`, toutes `SECURITY DEFINER`. Puis je me suis connecté avec un rôle à faibles privilèges qui peut exécuter du SQL et a le droit par défaut de créer des tables temporaires, et j’ai créé `pg_temp.admins` contenant l’utilisateur A, qui n’est pas admin :

![is_admin(A) après une table temp admins. Fonction SECURITY DEFINER, Postgres 17.10 local. Aucun 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/fr/supabase-function-search-path-mutable.svg)

*Ma reproduction sur un Postgres local, 24 octobre 2026.*

| Réglage de la fonction                                   | Condition du lint | `is_admin(A)` après la table temporaire |
| -------------------------------------------------------- | ----------------- | --------------------------------------- |
| aucun                                                    | signalée          | `true`                                  |
| `set search_path = public`                               | passe             | `true`                                  |
| `set search_path = ''`, le corps utilise `public.admins` | passe             | `false`                                 |
| `set search_path = public, pg_temp`                      | passe             | `false`                                 |

La deuxième ligne est celle qui m’a surpris. Un mainteneur de Supabase [a écrit en 2024](https://github.com/supabase/splinter/issues/100) qu’un chemin fixe non vide ne comporte aucun risque de sécurité, et le lint l’accepte. Mais Postgres cherche d’abord dans le schéma temporaire, sauf si `pg_temp` est nommé dans le chemin, comme l’avertit sa [documentation sur l’écriture de fonctions SECURITY DEFINER](https://www.postgresql.org/docs/current/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY). Avec `search_path = public`, une table temporaire peut donc encore masquer un nom non qualifié.

## Qui peut vraiment le faire sur Supabase ?

Pas un visiteur via votre application. La Data API ne peut ni créer de tables temporaires ni changer le `search_path` de la session ; la clé publishable seule n’y mène donc pas. Il faut une connexion directe à la base : un second service avec son propre identifiant, un outil de BI ou de reporting, le rôle en lecture seule d’un prestataire, une chaîne de connexion divulguée. Sur la plupart des petites applications, le risque est faible ; sur tout projet où d’autres personnes ou outils que vous détiennent des identifiants de base, corrigez d’abord les fonctions definer.

## Comment le corriger ?

Recréez la fonction avec un chemin vide et des noms de schéma dans le corps :

```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;
```

Le `revoke` compte autant que le chemin : une fonction definer dans `public` est appelable via la Data API ; sans lui, n’importe quel invité peut demander à `/rpc/is_admin` si un utilisateur donné est admin. Ne le rendez qu’aux rôles qui en ont besoin, ou gardez ces aides dans un schéma que l’API n’expose pas.

N’exécutez pas seulement `alter function public.is_admin(uuid) set search_path = ''`. Cela fait taire l’Advisor et casse la fonction : lors de mon essai, l’appel suivant a échoué avec `42P01: relation "admins" does not exist`. Les opérateurs d’extension ont aussi besoin de leur schéma, comme `operator(extensions.<=>)` pour pgvector.

Pour lister ce que le lint signalera dans `public`, fonctions definer en premier :

```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 correction a-t-elle un coût ?

Parfois. Toute clause `set` empêche Postgres d’intégrer une fonction SQL simple dans la requête qui l’appelle, [comme signalé sur GitHub](https://github.com/supabase/supabase/issues/33131). J’ai mesuré les deux formes sur 200 000 lignes avec un index sur la colonne filtrée (médiane de 7 exécutions) :

| Fonction SQL, puis filtrée par propriétaire                | Sans `set`              | `set search_path = ''` |
| ---------------------------------------------------------- | ----------------------- | ---------------------- |
| Filtre à l’extérieur : `visible_items() where owner = ...` | 0,053 ms, index utilisé | 36,7 ms, scan complet  |
| Filtre à l’intérieur : `items_of(owner)`                   | 0,056 ms                | 0,082 ms               |

Si une fonction SQL qui renvoie un ensemble est interrogée avec un filtre à l’extérieur, déplacez le filtre à l’intérieur comme paramètre avant de fixer le chemin.

## Vérifiez votre projet

- Dans l’Advisor, corrigez d’abord les fonctions `SECURITY DEFINER` de la liste ; les fonctions invoker s’exécutent de toute façon avec les droits de l’appelant.
- L’[audit de sécurité Supabase](https://madsalt.tech/fr/tools/supabase-security-check/) gratuit montre les constats de l’Advisor à côté de ses propres règles, y compris les fonctions definer appelables via l’API et celles qui vérifient `current_user`.
- Ce que les fonctions definer peuvent sauter est détaillé dans [Un NULL dans IF NOT a sauté le contrôle sur 3683 tâches](https://madsalt.tech/fr/notes/plpgsql-if-not-null-fails-open/), et les politiques qui appellent de telles aides dans [Supabase RLS policy : exemples testés via l’API](https://madsalt.tech/fr/blog/supabase-rls-policy-examples/).
