---
title: "Supabase search_path警告：意味と対処"
description: "Supabaseの警告function_search_path_mutableの意味、悪用できる人、修正方法を、自分のテストの結果と合わせて説明します。"
url: "https://madsalt.tech/ja/notes/supabase-function-search-path-mutable/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/ja/about/"
language: "ja"
published: 2026-11-01
---

# Supabase search_path警告：意味と対処

TL;DR

この警告は、関数に固定された`search_path`がないことを意味します。そのため、関数内の修飾されていない名前（たとえば`admins`）は、呼び出した側のセッションの設定どおりに探されます。私のローカルのテストでは、管理者ではないユーザーが`admins`という名前の一時テーブルを作ると、`SECURITY DEFINER`関数`is_admin()`はそのユーザーに`true`を返し、`search_path = public`に固定しても防げませんでした。有効だったのは、`set search_path = ''`にして、すべてのテーブルを`public.admins`と書く方法です。

SupabaseのSecurity Advisorは、これをlint 0011「Function Search Path Mutable」、レベルWARNとして表示し、詳細は「Function `public.x` has a role mutable search_path」です。この設定のない関数はすべて、`SECURITY INVOKER`のものも含めて指摘されるため、補助関数の多いプロジェクトでは一覧が長くなります。あるオープンソースのプロジェクトは、[1回のマイグレーションでそのうち99件を修正しました](https://github.com/bdougie/contributor.info/commit/3c73dda01df80faccb75278ec0f826a84e01a6d9)。

## 「role mutable search_path」とは？

`search_path`は、スキーマ名が前に付いていない名前をPostgresが探すスキーマの一覧です。独自の設定がない関数は、呼び出した側の一覧を使います。Supabaseの[lintのページ](https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable)は、これを空文字列に固定することを勧めています。そうすると、関数内のすべての参照を完全修飾で書かなければならなくなります。

`SECURITY DEFINER`関数では、これがとりわけ重要です。関数は所有者の権限、通常は`postgres`で実行され、RLSを迂回します。呼び出した側が関数の読む`admins`を操作できるなら、`postgres`として動くチェックを操作できることになります。

## 私のテストで悪用できた？

ローカルのPostgres 17.10で、管理者が1人いる`public.admins`テーブルと、同じ関数`select exists (select 1 from admins where user_id = uid)`の4つのバージョンを作りました。すべて`SECURITY DEFINER`です。次に、SQLを実行でき、一時テーブルを作るデフォルトの権限を持つ低権限のロールでログインし、管理者ではないユーザーAを入れた`pg_temp.admins`を作りました。

![一時テーブルadmins作成後のis_admin(A)。SECURITY DEFINER関数、ローカルのPostgres 17.10。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/ja/supabase-function-search-path-mutable.svg)

*ローカルのPostgresでの私の再現、2026年10月24日。*

| 関数の設定                                          | lintの判定 | 一時テーブル作成後の`is_admin(A)` |
| --------------------------------------------------- | ---------- | --------------------------------- |
| なし                                                | 指摘される | `true`                            |
| `set search_path = public`                          | 通る       | `true`                            |
| `set search_path = ''`、本文は`public.admins`を使用 | 通る       | `false`                           |
| `set search_path = public, pg_temp`                 | 通る       | `false`                           |

驚いたのは2行目です。Supabaseのメンテナーは、空でない固定パスにセキュリティ上のリスクはないと[2024年に書いて](https://github.com/supabase/splinter/issues/100)おり、lintもそれを受け入れます。しかしPostgresは、パスに`pg_temp`が明示されていない限り、まず一時スキーマを探します。[SECURITY DEFINER関数の書き方に関するドキュメント](https://www.postgresql.org/docs/current/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY)も警告しているとおりです。そのため`search_path = public`でも、一時テーブルが修飾されていない名前を覆い隠せてしまいます。

## Supabaseで実際にこれができるのは誰？

アプリ経由の訪問者ではありません。Data APIは一時テーブルを作ることも、セッションの`search_path`を変えることもできないので、公開キーだけでは届きません。必要なのはデータベースへの直接接続です。独自のログインを持つ別のサービス、BIやレポートのツール、業務委託先の読み取り専用ロール、漏れた接続文字列などです。小さなアプリの多くではリスクは低いですが、あなた以外の人やツールがデータベースのログインを持つプロジェクトでは、definer関数を最初に直してください。

## どう直す？

関数を、空のパスと、本文中のスキーマ名付きで作り直します。

```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`はパスと同じくらい重要です。`public`にあるdefiner関数はData API経由で呼び出せるため、これがないと、ゲストが`/rpc/is_admin`に特定のユーザーが管理者かどうかを問い合わせられます。必要なロールにだけ付け直すか、このような補助関数はAPIに公開されていないスキーマに置いてください。

`alter function public.is_admin(uuid) set search_path = ''`だけを実行してはいけません。Advisorは静かになりますが、関数が壊れます。私の実行では、次の呼び出しが`42P01: relation "admins" does not exist`で失敗しました。拡張機能の演算子にもスキーマが必要で、pgvectorなら`operator(extensions.<=>)`のように書きます。

lintが`public`で指摘するものを、definer関数を先頭にして一覧するには次のとおりです。

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

## 修正にコストはかかる？

場合によってはかかります。`set`句が付くと、Postgresは単純なSQL関数を呼び出し元のクエリにインライン展開できなくなります。[GitHubでも報告](https://github.com/supabase/supabase/issues/33131)されています。フィルター対象の列にインデックスのある200,000行で、両方の形を計測しました（7回の中央値）。

| SQL関数を所有者で絞り込む                             | `set`なし                  | `set search_path = ''` |
| ----------------------------------------------------- | -------------------------- | ---------------------- |
| 外側でフィルター：`visible_items() where owner = ...` | 0.053 ms、インデックス使用 | 36.7 ms、全件スキャン  |
| 内側でフィルター：`items_of(owner)`                   | 0.056 ms                   | 0.082 ms               |

集合を返すSQL関数を外側のフィルター付きで呼び出しているなら、パスを固定する前に、フィルターをパラメーターとして関数の中に移してください。

## プロジェクトを確認する

- Advisorでは、一覧にある`SECURITY DEFINER`関数を先に直してください。invoker関数はどのみち呼び出し元の権限で動きます。
- 無料の[Supabaseセキュリティチェック](https://madsalt.tech/ja/tools/supabase-security-check/)は、Advisorの指摘を独自のルールと並べて表示します。APIから呼び出せるdefiner関数や、`current_user`を確認するdefiner関数も含まれます。
- definer関数がすり抜けられるものについては[PL/pgSQLのIF NOTでNULLがチェックを飛ばした：3683タスク](https://madsalt.tech/ja/notes/plpgsql-if-not-null-fails-open/)に、そのような補助関数を呼ぶポリシーについては[Supabase RLSポリシー例：APIで検証済み](https://madsalt.tech/ja/blog/supabase-rls-policy-examples/)にあります。
