---
title: "Supabase Function Search Path Mutable: hva det betyr"
description: "Hva Advisor-advarselen function_search_path_mutable betyr, hvem som kan utnytte den, hvorfor search_path = public ikke holdt i testen min, og løsningen."
url: "https://madsalt.tech/no/notes/supabase-function-search-path-mutable/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/no/about/"
language: "no"
published: 2026-11-01
---

# Supabase Function Search Path Mutable: hva det betyr

TL;DR

Advarselen betyr at en funksjon mangler fast `search_path`, så hvert ukvalifiserte navn i den, som `admins`, slås opp slik den kallendes økt sier. I den lokale testen min returnerte en `SECURITY DEFINER`-funksjon `is_admin()` `true` for en ikke-admin etter at brukeren hadde opprettet en midlertidig tabell kalt `admins`, og å låse `search_path = public` stoppet det ikke. Det som virket: `set search_path = ''` med hver tabell skrevet som `public.admins`.

Supabases Security Advisor lister dette som lint 0011, «Function Search Path Mutable», nivå WARN, med detaljen «Function `public.x` has a role mutable search_path». Den flagger hver funksjon uten innstillingen, også `SECURITY INVOKER`-funksjoner, så i et prosjekt med mange hjelpere blir listen lang: ett åpen kildekode-prosjekt [rettet 99 av dem i én enkelt migrering](https://github.com/bdougie/contributor.info/commit/3c73dda01df80faccb75278ec0f826a84e01a6d9).

## Hva betyr «role mutable search_path»?

`search_path` er listen over skjemaer Postgres søker i etter et navn uten skjema foran. En funksjon uten egen innstilling bruker den kallendes liste. Supabases [lint-side](https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable) anbefaler å sette den til en tom streng, som tvinger hver referanse i funksjonen til å være fullt kvalifisert.

For en `SECURITY DEFINER`-funksjon betyr det mest: den kjører med eierens rettigheter, vanligvis `postgres`, som omgår RLS. Hvis den kallende kan styre hvilken `admins` den leser, styrer vedkommende en sjekk som kjører som `postgres`.

## Kan noen utnytte det i testen min?

På en lokal Postgres 17.10 laget jeg en tabell `public.admins` med én admin og fire versjoner av den samme funksjonen, `select exists (select 1 from admins where user_id = uid)`, alle `SECURITY DEFINER`. Så logget jeg inn som en rolle med lave rettigheter som kan kjøre SQL og har standardretten til å opprette midlertidige tabeller, og opprettet `pg_temp.admins` med bruker A, som ikke er admin:

![is_admin(A) etter en temp-tabell admins. SECURITY DEFINER-funksjon, lokal Postgres 17.10. Ingen 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/no/supabase-function-search-path-mutable.svg)

*Min gjenskaping på en lokal Postgres, 24. oktober 2026.*

| Funksjonens innstilling                                | Lint-betingelse | `is_admin(A)` etter den midlertidige tabellen |
| ------------------------------------------------------ | --------------- | --------------------------------------------- |
| ingen                                                  | flagget         | `true`                                        |
| `set search_path = public`                             | består          | `true`                                        |
| `set search_path = ''`, kroppen bruker `public.admins` | består          | `false`                                       |
| `set search_path = public, pg_temp`                    | består          | `false`                                       |

Den andre linjen er den som overrasket meg. En Supabase-maintainer [skrev i 2024](https://github.com/supabase/splinter/issues/100) at en fast ikke-tom sti ikke medfører noen sikkerhetsrisiko, og linten godtar den. Men Postgres søker i det midlertidige skjemaet først med mindre `pg_temp` er nevnt i stien, slik [dokumentasjonen om å skrive SECURITY DEFINER-funksjoner](https://www.postgresql.org/docs/current/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY) advarer om. Så med `search_path = public` kan en midlertidig tabell fortsatt skygge for et ukvalifisert navn.

## Hvem kan faktisk gjøre dette på Supabase?

Ikke en besøkende gjennom appen din. Data API-et kan verken opprette midlertidige tabeller eller endre øktens `search_path`, så publishable key alene kommer ikke dit. Det krever en direkte databaseforbindelse: en annen tjeneste med egen innlogging, et BI- eller rapporteringsverktøy, en konsulents skrivebeskyttede rolle, en lekket tilkoblingsstreng. I de fleste små apper er risikoen lav; i ethvert prosjekt der andre personer eller verktøy enn deg har databaseinnlogginger, rett definer-funksjonene først.

## Hvordan fikser du det?

Opprett funksjonen på nytt med tom sti og skjemanavn i kroppen:

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

`revoke` er like viktig som stien: en definer-funksjon i `public` kan kalles gjennom Data API-et, så uten den kan hvilken som helst gjest spørre `/rpc/is_admin` om en gitt bruker er admin. Gi den bare tilbake til rollene som trenger den, eller hold slike hjelpere i et skjema API-et ikke eksponerer.

Ikke kjør bare `alter function public.is_admin(uuid) set search_path = ''`. Det får Advisor til å tie og ødelegger funksjonen: i kjøringen min feilet neste kall med `42P01: relation "admins" does not exist`. Operatorer fra utvidelser trenger også skjemaet sitt, som `operator(extensions.<=>)` for pgvector.

Slik lister du det linten vil flagge i `public`, definer-funksjoner først:

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

## Koster løsningen noe?

Noen ganger. Enhver `set`-klausul hindrer Postgres i å bygge inn en enkel SQL-funksjon i spørringen som kaller den, [som rapportert på GitHub](https://github.com/supabase/supabase/issues/33131). Jeg målte begge formene på 200 000 rader med en indeks på den filtrerte kolonnen (median av 7 kjøringer):

| SQL-funksjon, deretter filtrert på eier             | Uten `set`             | `set search_path = ''` |
| --------------------------------------------------- | ---------------------- | ---------------------- |
| Filter utenfor: `visible_items() where owner = ...` | 0,053 ms, indeks brukt | 36,7 ms, full skanning |
| Filter innenfor: `items_of(owner)`                  | 0,056 ms               | 0,082 ms               |

Hvis en funksjon som returnerer et sett spørres med filter utenfor, flytt filteret inn som parameter før du låser stien.

## Sjekk prosjektet ditt

- Rett i Advisor først `SECURITY DEFINER`-funksjonene på listen; invoker-funksjoner kjører uansett med den kallendes rettigheter.
- Den gratis [Supabase-sikkerhetssjekken](https://madsalt.tech/no/tools/supabase-security-check/) viser Advisor-funn ved siden av sine egne regler, blant annet definer-funksjoner som kan kalles gjennom API-et og definer-funksjoner som sjekker `current_user`.
- Mer om hva definer-funksjoner kan hoppe over står i [NULL i PL/pgSQL IF NOT hoppet over sjekken på 3683 oppgaver](https://madsalt.tech/no/notes/plpgsql-if-not-null-fails-open/), og policyer som kaller slike hjelpere i [Supabase RLS policy-eksempler, testet via API-et](https://madsalt.tech/no/blog/supabase-rls-policy-examples/).
