Advisor警告の潰し方
Supabase の Security Advisor は警告を件数で見せる。 件数は問題の種類数ではないので、最初に出所と危険度へ畳んでから手をつける。
手順
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 で渡す。
関連
- 実際にこの手順で231件を潰した記録: 警告231件は231種類の問題ではなかった
- 可視範囲まわりの踏み方: RLSとアプリ側の絞り込み