Advisor警告の潰し方

更新: 2026-09-04
supabasepostgresqlセキュリティマイグレーション

Supabase の Security Advisor は警告を件数で見せる。 件数は問題の種類数ではないので、最初に出所と危険度へ畳んでから手をつける。

手順

1. 出所で分ける2. 危険度を見る3. 根本原因を直す4. 検証へ固定pg_depend で拡張か自前かを判定DEFINER のみ危険INVOKER は定型拡張を extensionsスキーマへ移す配置と制約数をassert する画面のエクスポートから始めると1で止まる。件数しか手に入らず、出所で切れない。
件数を見る前に出所へ畳む。危険度の判定が済むまで優先度を決めない。

1. 出所で分ける

Advisor の画面を使わず、本番と同一スキーマのローカルDBへ同じ判定を投げる。 外部APIを叩かず、件数ではなく内訳が返る。

select p.prosecdef,
       coalesce(e.extname, '(自前)') as source,
       count(*)
from pg_proc p
  join pg_namespace n on n.oid = p.pronamespace
  left join pg_depend d on d.objid = p.oid and d.deptype = 'e'
  left join pg_extension e on e.oid = d.refobjid
where n.nspname = 'public'
  and p.prokind in ('f','p')
  and not exists (
    select 1 from unnest(coalesce(p.proconfig,'{}')) c where c like 'search_path=%'
  )
group by 1, 2;

前提として、ローカルと本番の migration 適用状況が揃っていること。 ずれているとスキーマ由来の警告件数が一致しない。

2. 危険度は SECURITY DEFINER かどうかで決まる

function_search_path_mutable が実害になるのは、関数が定義者権限で動くときだけ。

種別search_path 未固定のとき対応
SECURITY DEFINER権限昇格の経路になる必ず固定する
SECURITY INVOKER呼び出し元の権限で動く。昇格しない定型対応として塞ぐ

DEFINER が全件固定済みなら、残りの警告が何百件あっても緊急ではない。 件数で優先度を決めない。

3. 自前の関数は定義に触らず設定だけ変える

alter function public.some_function(p_arg text) set search_path = public;

create or replace で書き直さない。定義の再現ミスが混ざる。

一括 migration は「やらないこと」をテストで縛る。

expect(sql).not.toMatch(/alter function public\.gbt_/);        // 拡張の関数へ触らない
expect(sql).not.toMatch(/create\s+(or\s+replace\s+)?function/i); // 定義を書き換えない
expect(sql).not.toMatch(/drop\s+function/i);

4. 拡張が public にいると警告が数百件になる

btree_gist のように関数を大量に作る拡張が public にあると、その関数すべてが function_search_path_mutable として数えられる。拡張の所有物なので alter function で個別に触ってはいけない。直すのは配置のほう。

alter extension btree_gist set schema extensions;

移す前に確認すること

確認理由
その拡張に依存する制約・インデックスの一覧exclude using gist などが黙って効かなくなるのが最悪
移設後に制約が実際に拒否するか存在確認だけでは不十分。重複データを投入して弾かれることを見る

実測では、排他制約は作成済みインデックスで動くため移設後も判定は変わらなかった。

移した後に効いてくる制約

新しく gist の排他制約を作る migration は、演算子クラスを解決できなくなる。 extensions を検索パスに入れるか、スキーマ修飾する。

set local search_path = public, extensions;

これを忘れると、既存環境では通ってクリーンな環境だけで落ちる migration ができる。

5. 検証スクリプトへ固定する

配置は元へ戻せてしまうので、クリーン環境の検証に assert を置く。

if not exists (
  select 1 from pg_extension e join pg_namespace n on n.oid = e.extnamespace
  where e.extname = 'btree_gist' and n.nspname = 'extensions'
) then
  raise exception 'btree_gist must live in the extensions schema';
end if;

落とし穴: 検証範囲を数値でベタ書きしない

migration 検証スクリプトに上限や本数を数値で書くと、migration を足すたびに 検証範囲が静かに縮む。 テストは通り続けるので気づけない。

# 悪い: 0122 までしか流れない
apply_range 86 122

# 良い: 最新版をファイルから求める
LATEST_VERSION="$(ls "${MIGRATIONS_DIR}" | grep -E '^[0-9]{4}_.*\.sql$' | sed 's/_.*//' | sort -n | tail -1)"
apply_range 86 "$((10#${LATEST_VERSION}))"

期待件数も同様に、ファイル数から求めて psql -v で渡す。

関連

WIKI一覧へ