メインコンテンツへスキップ
友田 陽大
アプリ層セキュリティ
Supabase
PostgreSQL
RLS
セキュリティ

Supabaseの SECURITY DEFINER 関数の落とし穴 — search_path 未固定が RLS 迂回・権限昇格を生む

SECURITY DEFINER関数は定義者権限で動くため、search_pathを固定しないと攻撃者が一時スキーマやpublicに同名オブジェクトを差し込み、RLSを迂回して権限昇格できます。set search_path = '' とスキーマ修飾、GRANT最小化という安全な書き方と、migrations/pg_procでの検出方法を実SQLで解説します。

公開日
読了時間
21分
著者
友田 陽大
シェア

最初に結論を述べます。Supabaseのデータベース関数(RPC)が SECURITY DEFINER で定義され、かつ search_path を固定していないと、認証済みの一般ユーザーが「定義者(多くの場合 postgres)の権限」で任意のオブジェクトをすり替えられ、RLSを迂回して権限昇格できます。 これはSupabase固有のバグではありません。PostgreSQLの SECURITY DEFINER が昔から持つ古典的な落とし穴が、テーブルや関数を自動でREST/RPCとして公開するSupabaseという環境で、攻撃面として顕在化したものです。

行レベルセキュリティ(RLS)を完璧に張っても、その「外側」にこの穴は空きます。SECURITY DEFINER 関数は定義者の権限で動き、定義者がテーブル所有者ならRLSを飛び越えるからです。本記事は、(1) SECURITY DEFINERSECURITY INVOKER の違い、(2) なぜ関数・RPCがRLSを迂回しうるのか、(3) search_path 未固定がどう攻撃に化けるのか、(4) 脆弱→修正の実SQL、(5) 検出の機械化、を一次情報と実コードで解説します。そして最後に正直な線引きをします——この穴の「検出」は機械化できますが、「その関数の権限設計が正しいか」は人間の判断です。これはアプリ層セキュリティ全体の地図の一部で、全体像はNext.js × Supabase アプリケーションセキュリティ完全ガイドに整理しています。


1. SECURITY DEFINER と SECURITY INVOKER —— 誰の権限で実行されるか

PostgreSQLの関数には、実行時に「誰の権限を使うか」を決める2つのモードがあります。

  • SECURITY INVOKER(既定) —— 関数を呼び出したユーザーの権限で実行される。呼び出し元が authenticated なら、関数の中身も authenticated の権限で動く。
  • SECURITY DEFINER —— 関数を**作成・所有するユーザー(定義者)**の権限で実行される。Unixの setuid プログラムと同じで、呼び出し元が誰であっても、中身は定義者の権限で走る。

公式ドキュメント(PostgreSQL: CREATE FUNCTION)はこう述べています——「SECURITY DEFINER 関数は、それを所有するユーザーの権限で実行されるため、誤用されないよう注意が必要だ」。つまり SECURITY DEFINER は、意図的に権限の壁を越えるための機能です。便利ですが、越える先が高権限(Supabaseでは多くの関数が postgres 所有)なら、設計を一つ誤るだけで権限昇格の踏み台になります。

観点SECURITY INVOKER(既定)SECURITY DEFINER
実行時の権限呼び出したユーザー関数の定義者(所有者)
RLSの効き方呼び出し元に対して効く定義者がテーブル所有者なら飛び越える
典型用途通常のクエリ・計算一般ユーザーに権限を「貸す」処理(集計・通知・横断更新)
危険度低い(権限の拡大が無い)高い(権限の壁を越える)

SECURITY DEFINER 自体は悪ではありません。正当な用途もあります——「一般ユーザーには直接 select させたくないテーブルから、集計値だけを関数経由で返す」「監査ログのように、本人には更新させたくない行を関数の内側だけで追記する」といったケースです。鍵は、権限を貸す範囲を関数の内側に閉じ込め、貸した先で攻撃者に主導権を渡さないこと。search_path 未固定は、まさにこの「貸した先」で主導権を奪われる経路です。

重要なのは、RLS迂回の起点はこの「権限の差」そのものだという点です。SECURITY INVOKER の関数なら、search_path を乗っ取られても呼び出し元の権限以上のことはできません。SECURITY DEFINER だからこそ、乗っ取りが「権限昇格」に化けます。だから最初の問いは常に「この関数は本当に SECURITY DEFINER である必要があるか」です(第5節で詳述)。


2. なぜ関数・RPC が RLS を「飛び越える」のか

Supabaseは、データベース関数を PostgREST 経由で自動的にRPCエンドポイントとして公開します。anon / authenticated ロールに EXECUTE 権限があれば、ブラウザから次のように呼べます。

# データベース関数 is_admin() を RPC として呼ぶ(anon キーだけで叩ける)
curl "https://<project>.supabase.co/rest/v1/rpc/is_admin" \
  -H "apikey: <anon-key>" \
  -H "Authorization: Bearer <user-jwt>" \
  -H "Content-Type: application/json" -d '{}'

ここでRLSとの関係が問題になります。PostgreSQLのRLSは「現在のユーザー」を基準に評価されますが、公式ドキュメント(PostgreSQL: Row Security Policies)が明記するとおり、テーブルの所有者は通常RLSをバイパスしFORCE ROW LEVEL SECURITY を明示しない限り)、スーパーユーザーや BYPASSRLS 属性を持つロールは常にバイパスします。

Supabaseのダッシュボードやマイグレーションで作った関数は、多くの場合 postgres が所有します。postgrespublic スキーマのテーブルも所有しています。したがって——

SECURITY DEFINER 関数(postgres 所有)の中で public のテーブルを触ると、その問い合わせは postgres として実行され、RLSが効かない

これは、サーバー側で service_role キーを使ってRLSを飛び越えるのと同じ構図を、データベースの内側で作っていることになります。service_role キーの危険性とRLSバイパスについてはanonキーとservice_roleキーの露出で扱っていますが、SECURITY DEFINER 関数は「鍵を漏らしていないのに、DBの中に同じバイパス経路を作ってしまう」点で見落とされやすい穴です。

-- 例:profiles に完璧な RLS を張っていても…
alter table public.profiles enable row level security;
create policy "read own profile" on public.profiles
  for select to authenticated using ( (select auth.uid()) = id );

-- この SECURITY DEFINER 関数経由なら、RLS を素通りして全行を集計できてしまう
create function public.count_all_profiles()
returns bigint language sql security definer as $$
  select count(*) from public.profiles;  -- postgres 権限=RLS が効かない
$$;

count_all_profiles() 単体は「件数を返すだけ」で無害に見えます。しかし**「RLSが効かない実行コンテキスト」をRPCとして外に開けた**こと自体がリスクの本体です。次節で見るように、search_path が未固定だと、この実行コンテキストを攻撃者に乗っ取られます。


3. search_path 未固定という攻撃面

search_path は、スキーマ修飾なしで書かれたオブジェクト名(テーブル・関数・型・演算子)を、どのスキーマから順に探すかを決める設定です。SECURITY DEFINER 関数に SET search_path を付けないと、関数は呼び出し側の search_path をそのまま継承します。

ここに攻撃の余地が生まれます。SECURITY DEFINER 関数の本体が、オブジェクトを非修飾で参照していると、その名前が「攻撃者の用意した別物」に解決されうるのです。

3-1. 脆弱な関数:非修飾の参照を持つ SECURITY DEFINER

-- 脆弱:SECURITY DEFINER なのに search_path 未固定。本体は profiles を非修飾で参照
create function public.is_admin()
returns boolean
language plpgsql
security definer                 -- 定義者(postgres)権限で動く=RLS を飛び越える
as $$
declare
  result boolean;
begin
  -- ↓ 非修飾の "profiles"。search_path 次第で別のオブジェクトに解決されうる
  select role = 'admin' into result
  from profiles
  where id = auth.uid();
  return coalesce(result, false);
end;
$$;

3-2. 攻撃:一時スキーマ・public に「同名の別物」を差し込む

攻撃者は認証済みの一般ユーザーで構いません。やることは、is_admin() の中の非修飾 profiles を、自分が作った同名オブジェクトにすり替えることです。

-- 攻撃者(authenticated):一時テーブルで profiles を差し替える
create temp table profiles (id uuid, role text);
insert into profiles values (auth.uid(), 'admin');

-- pg_temp(一時スキーマ)は、非修飾のテーブル参照では既定で「最初」に探索される。
-- そのため is_admin() 内の "profiles" が攻撃者の一時テーブルに化け、
-- 定義者(postgres)権限で実行されている関数が "admin" を返す
select public.is_admin();   -- → true(本来 admin でないのに昇格)

なぜ一時テーブルが効くのか。ここはPostgreSQLの探索規則の中でも誤解されやすい点なので、正確に押さえます。

  • テーブル・ビュー・型などのリレーション名:一時スキーマ pg_temp は、search_path に明示されていない場合、既定で最初に探索されますpg_catalog よりも前)。そして一時テーブルの作成権限(TEMP)は既定で全ユーザーに付与されているため、誰でもこの差し替えを実行できます。これが最も普遍的に成立する攻撃ベクトルです。
  • 関数・演算子名pg_temp からは解決されません。ただし、search_path に「攻撃者が CREATE 権限を持つスキーマ」(典型的には public)が含まれていれば、そこに同名関数を仕込んで乗っ取れます。

つまり search_path 未固定の SECURITY DEFINER 関数は、「攻撃者が書き込めるスキーマに置いた同名オブジェクト」で、定義者権限のコードを乗っ取られるのが本質です。is_admin() のように戻り値が認可判断に使われていれば権限昇格に、SECURITY DEFINER 関数の中で更新や EXECUTE を行っていれば任意処理の実行につながります。公式のCREATE FUNCTIONも、この「信頼できないユーザーが書き込めるスキーマを探索経路に含めないこと」を SECURITY DEFINER の必須の注意点として挙げています。


4. 安全パターン —— 脆弱→修正の実SQL

防御の核心は、**「探索経路を呼び出し側に委ねない」かつ「オブジェクトを攻撃者がすり替えられないよう特定する」**の2点です。具体的には3つの対策を同時に適用します。

4-1. search_path を固定し、すべてをスキーマ修飾する(Supabase 推奨の最厳)

-- 修正:search_path を空に固定し、本体のオブジェクトをすべてスキーマ修飾する
create or replace function public.is_admin()
returns boolean
language plpgsql
security definer
set search_path = ''             -- ← 呼び出し側の search_path を無効化する
as $$
declare
  result boolean;
begin
  select role = 'admin' into result
  from public.profiles            -- ← スキーマ修飾。一時スキーマや public 差し込みに化けない
  where id = (select auth.uid()); -- ← auth.uid() も auth スキーマで修飾済み
  return coalesce(result, false);
end;
$$;

ここで正直に補足すべき重要な点があります。set search_path = '' は「魔法の盾」ではありません。空にすると、非修飾の名前は pg_catalog(組み込み関数)以外ほぼ解決できなくなり、書き忘れがエラーとして表面化する——これが狙いです。しかし前述のとおり、非修飾のリレーション名は空の search_path でも pg_temp が先に探索されます。したがって実際に攻撃を無効化しているのは「スキーマ修飾」そのものであり、set search_path = '' は「修飾し忘れを黙って public に落とさず、必ず明示させる」ための土台です。'' を付けただけで本体に非修飾参照が残っていれば、依然として乗っ取られえます。両者は必ずセットです。

pg_catalogsearch_path を空にしても暗黙に探索されるため、now()lower() などの組み込み関数は引き続き使えます。修飾が要るのは、publicauth など自前/拡張スキーマのオブジェクトです。

4-2. 代替:信頼スキーマに固定し、pg_temp を必ず最後に置く

set search_path = '' が厳しすぎる(既存の非修飾参照が多い)場合、PostgreSQL公式が示す作法は**「信頼できるスキーマに固定し、pg_temp を末尾に置く」**ことです。末尾に置けば、一時スキーマは最後にしか探索されず、リレーションのすり替えを無効化できます。

-- 代替:信頼スキーマを先に、pg_temp を最後に固定する(公式 CREATE FUNCTION の作法)
create or replace function public.is_admin()
returns boolean
language plpgsql
security definer
set search_path = public, pg_temp   -- 信頼スキーマ → pg_temp は必ず末尾
as $$ ... $$;

ただしこの形は、public 自体が攻撃者に書き込み可能でないことが前提です。PostgreSQL 15以降は public への CREATE がデフォルトで PUBLIC に付与されなくなったため成立しやすくなりましたが、古い設定を引き継いだプロジェクトでは要確認です。迷ったら最厳の set search_path = '' + 完全修飾を選ぶのが安全側です。

4-3. GRANT を最小化する —— 誰が呼べるかを絞る

CREATE FUNCTION は、既定で PUBLIC(全ロール)に EXECUTE を付与します。Supabaseでは、これがそのまま「anon でも叩けるRPC」になります。SECURITY DEFINER 関数では、まず暗黙の EXECUTE を剥がし、必要なロールにだけ与え直します。

-- GRANT 最小化:PUBLIC への暗黙の EXECUTE を剥がし、必要なロールにだけ許可する
revoke execute on function public.is_admin() from public;
grant execute on function public.is_admin() to authenticated;
-- anon(未ログイン)に開ける必要が無いなら、絶対に grant しない

これは「探索経路の固定」とは別軸の防御です。search_path を固定しても、そもそも anon に開ける必要のない権限昇格関数を anon が叩ける状態なら、攻撃面は広いままです。「誰の権限で動くか(DEFINER)」と「誰が呼べるか(GRANT)」は独立に最小化します。

4-4. そもそも SECURITY DEFINER が要るかを問う

最も効くのは、危険な機能を使わないで済ませることです。多くの関数は SECURITY INVOKER(既定)で十分で、その場合RLSは呼び出し元に対して正しく効き、search_path 乗っ取りも権限昇格になりません(呼び出し元の権限を超えられないため)。SECURITY DEFINER を選ぶのは、「一般ユーザーの権限では届かない処理を、限定的に肩代わりさせる」明確な理由があるときだけにします。PostgreSQL 15以降は SECURITY INVOKER を明示できるので、意図をコードに残しておくとレビューが楽になります。


5. Supabase で見かける危険な RPC パターン

実際の現場で繰り返し見かける形を挙げます。いずれも「動く」ので、デモやレビューをすり抜けます。

パターンA:管理機能を SECURITY DEFINER で公開し、GRANT も搾れていない

-- 危険:ロール昇格を SECURITY DEFINER で公開。search_path 未固定 + 既定で PUBLIC に EXECUTE
create function public.promote_to_admin(target uuid)
returns void
language sql
security definer            -- RLS を飛び越えて profiles を更新できる
as $$
  update profiles set role = 'admin' where id = target;  -- 非修飾&所有権チェックなし
$$;
-- 既定の PUBLIC EXECUTE が残るため、anon/authenticated から rpc/promote_to_admin を叩ける

この関数には3つの欠陥が同居しています。(1) search_path 未固定(非修飾 profiles)、(2) PUBLIC への EXECUTE が残存、そして (3) 「呼び出し元が誰であれ、任意の target を admin にできる」という認可ロジックそのものの欠陥です。

ここが分かれ道です。(1) と (2) は機械的に検出・修正できます。set search_path = '' を足し、revoke ... from public すればよい。しかし (3) は search_path を完璧に固めても残ります。 「この関数は誰が呼んでよく、誰を昇格してよいのか」——update public.profiles set role = 'admin' where id = target and <呼び出し元が管理者である条件> のような認可判断は、あなたの業務ルールにしか答えがありません。これが本記事の正直な核心です(第8節)。

パターンB:RLSが効かない集計・横断取得を素通しで開ける

-- 危険:ダッシュボード用の横断集計を SECURITY DEFINER で公開(search_path 未固定)
create function public.org_dashboard(org uuid)
returns table(total int, revenue numeric)
language sql
security definer
as $$
  select count(*), coalesce(sum(amount), 0)
  from invoices where org_id = org;   -- 非修飾&「呼び出し元がこの org に属するか」未検証
$$;

便利な「横断取得」ほど SECURITY DEFINER にされがちで、しかも org をクライアントから受け取って所有権を確かめない——これは関数の内側で起こす IDOR(オブジェクト単位の認可欠陥)です。修正は search_path 固定+スキーマ修飾に加えて、本体に and exists (select 1 from public.memberships where user_id = auth.uid() and org_id = org) のような所有権条件を入れること。やはり機械化できるのは前半(探索経路の安全化)までで、所有権条件の定義は設計です。


6. 検出 —— migrations と pg_proc の静的検証

SECURITY DEFINER かつ search_path 未固定」というは、機械的に洗い出せます。ここは自動化が本領を発揮する領域です。

6-1. 稼働中DBを直接調べる(pg_proc)

カタログ pg_proc には、関数が SECURITY DEFINER か(prosecdef)、どんな SET を持つか(proconfig)が記録されています。これを使えば一覧化できます。

-- SECURITY DEFINER 関数のうち search_path を固定していないものを洗い出す
select
  n.nspname  as schema,
  p.proname  as function,
  pg_get_userbyid(p.proowner) as owner,   -- 定義者=この権限で動く
  p.proconfig as config                   -- SET 句。null なら呼び出し側を継承
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
where p.prosecdef                          -- SECURITY DEFINER のみ
  and n.nspname not in ('pg_catalog', 'information_schema')
  and (
    p.proconfig is null
    or not exists (
      select 1 from unnest(p.proconfig) as cfg
      where cfg like 'search_path=%'
    )
  )
order by 1, 2;

これで「未固定の SECURITY DEFINER 関数」がゼロ件であることを、リリース前のチェックとして確認できます。SupabaseのDatabase Linterにも、同種を警告する function_search_path_mutable ルールがあります。

6-2. migrations を静的検証する(CIで番をさせる)

稼働DBに繋がなくても、supabase/migrations/**.sql を読めば同じ欠陥を見つけられます。RLS設定ミスの体系的な洗い出しはRLS設定ミスの検出と監査にまとめていますが、SECURITY DEFINER 関数については特に次の3点をマイグレーションから機械的にフラグします。

  • security definer を含むが、同じ定義に set search_path が無い
  • set search_path はあるが、空でも pg_temp 末尾でもない(=攻撃者書き込み可能なスキーマが先頭にありうる)
  • 関数定義の後に revoke execute ... from public が無い(既定の PUBLIC EXECUTE が残存)

私が公開しているOSS Aegis は、supabase/migrations を解析してこれらを検出します。インストール不要で走ります。

# インストール不要・設定不要でスキャン(未固定の SECURITY DEFINER/過剰 GRANT を可視化)
npx @aegiskit/cli scan

正規表現だけだと $$ ... $$ の本体や複数行定義で取りこぼすため、実用にはSQLをパースして「定義ブロック単位」で属性を見るのが確実です。重要なのは、この一次フィルタをCIに常設し、未固定の SECURITY DEFINER が新たに混入したらビルドを止めることです。人間の記憶ではなく、機械に番をさせます。


7. 本番前チェックリスト

外注でもAI生成でも、SECURITY DEFINER 関数を本番に出す前に最低限これだけは確認してください。

  • そもそも SECURITY DEFINER が必要か。SECURITY INVOKER(既定)で足りないかを先に問うた
  • すべての SECURITY DEFINER 関数に set search_path = ''(または信頼スキーマ+pg_temp 末尾)を明示している
  • 関数本体のテーブル・型・関数はすべてスキーマ修飾public.fooauth.uid())。非修飾参照がゼロ
  • set search_path = '' を「付けただけ」で満足せず、修飾とセットになっている
  • 関数定義の直後に revoke execute ... from public し、必要なロールにだけ grant している
  • anon(未ログイン)に開ける必要のない関数を、anon から呼べる状態にしていない
  • 関数本体に、呼び出し元の所有権・権限を確かめる条件が入っている(昇格・横断取得・更新系は特に)
  • pg_proc クエリ(第6節)で「未固定の SECURITY DEFINER」がゼロ件であることを確認した
  • migrations の静的検証(npx @aegiskit/cli scan 等)をCIに常設している

発注者・レビュアーの視点で最も効く質問は、**「この関数はなぜ SECURITY DEFINER なのですか?」「search_path は固定していますか?」「これは誰が呼べますか(anon でも叩けますか)?」**の3つです。設計を理解している開発者なら即答できます。


8. どこまで機械化でき、どこから設計か(正直に)

最後に、線を引きます。誇張は信頼を損なうので、できることとできないことを分けて述べます。

機械化できること(検出・警告)。SECURITY DEFINER かつ search_path 未固定」「非修飾参照の残存」「PUBLIC への過剰 EXECUTE」——これらはの問題なので、pg_proc や migrations の静的検証で機械的に洗い出せます。OWASPのApplication Security Verification Standard(ASVS)が説く「セキュリティは『入れたか』ではなく『検証できるか』で測る」という規律に、この層はよく合致します。まずは Aegis(無料OSS、npx @aegiskit/cli scan)で現状を可視化するのが、最もコスパの良い第一歩です。

機械化できないこと(設計判断)。 一方で、ツールが答えられない問いが残ります——「この関数は本当に SECURITY DEFINER であるべきか」「定義者の権限で何をどこまで肩代わりさせてよいか」「誰がこのRPCを呼んでよいか」「本体の認可ロジック(所有権・権限の条件)は正しいか」。第5節の promote_to_admin が示したとおり、search_path を完璧に固めても、関数のロジックが『誰でも誰でも昇格できる』なら依然として脆弱です。これらはあなたのデータモデルと業務ルールを理解した人間にしか判断できません。いかなるツールも、search_path 未固定という罠は検出できても、関数の権限設計が『正しい』ことは証明しません。「スキャンが通った=よくある罠は踏んでいない」であって、「安全になった」ではない——この区別を曖昧にする製品は、むしろ油断を生みます。

だからこそ線引きが要ります。どこまで自動の検出で固め、どこから人間のレビューが要るか。 SECURITY DEFINER 関数の権限設計や、既存Supabaseアプリの認可・RLSレビューが必要なら、セキュリティ監査で承ります。私自身、木材流通業界のDX案件で、RLS・テナント分離・権限境界を含むデータ層の認可を実運用で設計・検証してきました。検出の機械化は、人間が本当に難しい設計判断に集中するための土台です。


よくある質問(FAQ)

Q. set search_path = '' を付ければ、それで安全になりますか? A. それだけでは不十分です。空にする目的は「非修飾参照をエラーとして表面化させ、必ずスキーマ修飾を強制する」ことであり、実際に攻撃を無効化しているのはスキーマ修飾そのものです。非修飾のリレーション名は空の search_path でも一時スキーマ(pg_temp)が先に探索されるため、'' を付けても本体に非修飾参照が残れば乗っ取られえます。''(固定)と完全修飾は必ずセットにしてください。

Q. すべての関数で search_path を固定すべきですか? A. SECURITY DEFINER 関数では必須です。SECURITY INVOKER(既定)でも固定する習慣は良い(挙動が呼び出し側に左右されなくなる)ですが、優先順位は圧倒的に SECURITY DEFINER が先。権限昇格に直結するのはそちらだからです。

Q. RLSさえ正しく張れば、SECURITY DEFINER の心配は要りませんか? A. いいえ。SECURITY DEFINER 関数は定義者(多くは postgres=テーブル所有者)の権限で動くため、RLSを飛び越えます。RLSは「呼び出し元の権限」に対して効くもので、定義者権限で実行されるコードには効きません。RLSとは独立に、関数側で search_path 固定・修飾・GRANT最小化・認可ロジックを担保する必要があります。

Q. 一般ユーザーが本当に一時テーブルなんて作れるのですか? A. 作れます。一時オブジェクトの作成権限(TEMP)は、PostgreSQLでは既定で全ロールに付与されています。public への CREATE は新しい環境では絞られていることが多い一方、pg_temp 経由のリレーションすり替えはより普遍的に成立するため、SECURITY DEFINER の主要な攻撃面として常に想定すべきです。

Q. 既存の大量の関数を、どう棚卸しすればいいですか? A. まず第6節の pg_proc クエリで「未固定の SECURITY DEFINER」を全件抽出し、件数を把握します。次に npx @aegiskit/cli scan で migrations 側も突き合わせ、CIに常設して再発を止めます。修正は「固定+修飾+GRANT最小化」をテンプレ化すれば機械的に進みますが、各関数の認可ロジックの妥当性だけは1本ずつ人間が確認してください。


まとめ:探索経路を呼び出し側に委ねない

要点を整理します。

  • SECURITY DEFINER 関数は定義者の権限で動く。Supabaseでは多くが postgres 所有のため、RPCとして呼ぶとRLSを飛び越える。RLS迂回の起点は SECURITY INVOKER(既定)との「権限の差」そのもの。
  • search_path を固定しないと、本体の非修飾のテーブル/関数参照が呼び出し側の探索経路で解決される。攻撃者は一時スキーマ(pg_temp)や public に同名オブジェクトを差し込み、定義者権限で自分のコードを実行させられる(権限昇格)。
  • 安全パターンは3点セット——(1) set search_path = ''(または信頼スキーマ+pg_temp 末尾)に固定、(2) オブジェクトをすべてスキーマ修飾、(3) EXECUTEPUBLIC から剥がし必要なロールにだけ grant'' は修飾の強制装置であって、修飾とセットで初めて効く。
  • 「未固定の SECURITY DEFINER」というは、pg_proc や migrations の静的検証で機械的に検出でき、CIに番をさせられる。
  • ただし「その関数を SECURITY DEFINER にすべきか」「誰に呼ばせるか」「本体の認可ロジックが正しいか」は設計判断。ツールは罠を検出できても、権限設計の正しさは証明しない。

AIで速く作ること自体は正しい。速く作ったSupabaseアプリのデータ層に潜む SECURITY DEFINER の落とし穴を検出・修正し、権限設計のレビューが必要であれば、お気軽にご相談ください。


参考資料

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事の対策、ツールで自動化できます

Next.js / Supabase のセキュリティ統制を、OSS の Aegis で自動化

この記事の対策の多くは、ミドルウェア1枚と静的解析で機械的に検出・強化できます。無料・MIT の Aegis なら、いまのプロジェクトを1コマンドからスキャンできます。設計が要る「縦のリスク」は監査でも承ります。

プロジェクト単位(請負)・技術顧問のどちらにも対応可能です。まずは30分の無料技術相談から。