---
title: "Supabase „function search path mutable“: co to znamená"
description: "Co znamená varování Advisoru function_search_path_mutable, kdo ho může zneužít, proč search_path = public v mém testu nestačil a jak to opravit."
url: "https://madsalt.tech/cs/notes/supabase-function-search-path-mutable/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/cs/about/"
language: "cs"
published: 2026-11-01
---

# Supabase „function search path mutable“: co to znamená

TL;DR

Varování znamená, že funkce nemá pevný `search_path`, takže každé nekvalifikované jméno uvnitř ní, například `admins`, se hledá tak, jak říká session volajícího. V mém lokálním testu vrátila funkce `is_admin()` s `SECURITY DEFINER` pro uživatele, který není admin, hodnotu `true`, poté co si vytvořil dočasnou tabulku jménem `admins`, a ani pevné `search_path = public` to nezastavilo. Fungovalo `set search_path = ''` s každou tabulkou zapsanou jako `public.admins`.

Security Advisor od Supabase to uvádí jako lint 0011, „Function Search Path Mutable“, úroveň WARN, s detailem „Function `public.x` has a role mutable search_path“. Označí každou funkci bez tohoto nastavení, včetně těch se `SECURITY INVOKER`, takže u projektu s mnoha pomocnými funkcemi je seznam dlouhý: jeden open-source projekt [opravil 99 z nich jednou migrací](https://github.com/bdougie/contributor.info/commit/3c73dda01df80faccb75278ec0f826a84e01a6d9).

## Co znamená „role mutable search_path“?

`search_path` je seznam schémat, ve kterých Postgres hledá jméno bez schématu před ním. Funkce bez vlastního nastavení použije seznam volajícího. [Stránka lintu](https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable) Supabase doporučuje nastavit ho na prázdný řetězec, což vynutí plně kvalifikované každé odkazování uvnitř funkce.

U funkce se `SECURITY DEFINER` to záleží nejvíc: běží s právy svého vlastníka, obvykle `postgres`, který RLS obchází. Pokud volající může řídit, kterou tabulku `admins` funkce čte, řídí kontrolu, která běží jako `postgres`.

## Dá se to v mém testu zneužít?

Na lokálním Postgresu 17.10 jsem vytvořil tabulku `public.admins` s jedním adminem a čtyři verze téže funkce, `select exists (select 1 from admins where user_id = uid)`, všechny se `SECURITY DEFINER`. Pak jsem se přihlásil jako role s nízkými oprávněními, která smí spouštět SQL a má výchozí právo vytvářet dočasné tabulky, a vytvořil `pg_temp.admins` obsahující uživatele A, který admin není:

![is_admin(A) po dočasné tabulce admins. Funkce SECURITY DEFINER, lokální Postgres 17.10. Bez 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/cs/supabase-function-search-path-mutable.svg)

*Moje reprodukce na lokálním Postgresu, 24. října 2026.*

| Nastavení funkce                                     | Podmínka lintu | `is_admin(A)` po dočasné tabulce |
| ---------------------------------------------------- | -------------- | -------------------------------- |
| žádné                                                | označeno       | `true`                           |
| `set search_path = public`                           | projde         | `true`                           |
| `set search_path = ''`, tělo používá `public.admins` | projde         | `false`                          |
| `set search_path = public, pg_temp`                  | projde         | `false`                          |

Druhý řádek mě překvapil. Správce Supabase [v roce 2024 napsal](https://github.com/supabase/splinter/issues/100), že pevná neprázdná cesta nenese žádné bezpečnostní riziko, a lint ji přijímá. Postgres ale hledá nejdřív v dočasném schématu, pokud v cestě není uvedeno `pg_temp`, jak varují jeho [dokumenty o psaní funkcí SECURITY DEFINER](https://www.postgresql.org/docs/current/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY). Takže `search_path = public` stále nechá dočasnou tabulku zastínit nekvalifikované jméno.

## Kdo to na Supabase opravdu může udělat?

Ne návštěvník přes vaši aplikaci. Data API neumí vytvářet dočasné tabulky ani měnit `search_path` session, takže samotný publishable key to nezvládne. Je potřeba přímé připojení k databázi: druhá služba s vlastním přihlášením, BI nebo reportovací nástroj, read-only role dodavatele, uniklý connection string. U většiny malých aplikací je riziko nízké; u každého projektu, kde přihlášení k databázi drží jiní lidé nebo nástroje než vy, opravte nejdřív definer funkce.

## Jak to opravit?

Vytvořte funkci znovu s prázdnou cestou a jmény schémat v těle:

```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` je stejně důležité jako cesta: definer funkce v `public` je volatelná přes Data API, takže bez něj se kterýkoli host může přes `/rpc/is_admin` zeptat, zda je daný uživatel admin. Vraťte ho jen rolím, které ho potřebují, nebo držte takové pomocné funkce ve schématu, které API nevystavuje.

Nespouštějte jen `alter function public.is_admin(uuid) set search_path = ''`. Umlčí to Advisor a rozbije funkci: v mém běhu další volání selhalo s `42P01: relation "admins" does not exist`. Operátory rozšíření také potřebují své schéma, například `operator(extensions.<=>)` pro pgvector.

Seznam toho, co lint v `public` označí, s definer funkcemi první:

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

## Stojí oprava něco?

Někdy. Jakákoli klauzule `set` brání Postgresu vložit prostou SQL funkci do dotazu, který ji volá, jak [uvádí GitHub](https://github.com/supabase/supabase/issues/33131). Změřil jsem oba tvary na 200 000 řádcích s indexem na filtrovaném sloupci (medián ze 7 běhů):

| SQL funkce, pak filtrovaná podle vlastníka       | Bez `set`              | `set search_path = ''` |
| ------------------------------------------------ | ---------------------- | ---------------------- |
| Filtr venku: `visible_items() where owner = ...` | 0,053 ms, index použit | 36,7 ms, full scan     |
| Filtr uvnitř: `items_of(owner)`                  | 0,056 ms               | 0,082 ms               |

Pokud se funkce SQL vracející množinu dotazuje s filtrem mimo ni, přesuňte filtr dovnitř jako parametr, než pevně nastavíte cestu.

## Zkontrolujte svůj projekt

- V Advisoru opravte nejdřív funkce `SECURITY DEFINER` na seznamu; invoker funkce stejně běží s právy volajícího.
- Bezplatný [bezpečnostní checker pro Supabase](https://madsalt.tech/cs/tools/supabase-security-check/) ukazuje nálezy Advisoru vedle vlastních pravidel, včetně definer funkcí volatelných přes API a definer funkcí, které kontrolují `current_user`.
- Víc o tom, co definer funkce mohou přeskočit, je v poznámce [NULL v PL/pgSQL IF NOT přeskočil kontrolu u 3683 úkolů](https://madsalt.tech/cs/notes/plpgsql-if-not-null-fails-open/) a politiky, které takové pomocné funkce volají, jsou v článku [Supabase RLS policy: příklady otestované přes API](https://madsalt.tech/cs/blog/supabase-rls-policy-examples/).
