Chuyển đến nội dung chính
友田 陽大
Application-layer security
Supabase
PostgreSQL
RLS
セキュリティ

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

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

Published
Reading time
21 min read
Author
友田 陽大
Chia sẻ

最初に結論を述べます。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 の落とし穴を検出・修正し、権限設計のレビューが必要であれば、お気軽にご相談ください。


参考資料

友田

友田 陽大

Người phát triển sản phẩm đoạt Giải thưởng Bộ trưởng (METI). Với TypeScript + Python + AWS, tôi một mình triển khai trọn gói SaaS, chuyển đổi số công nghiệp và AI tạo sinh (RAG) sẵn sàng cho production — từ phân tích yêu cầu đến hạ tầng và vận hành.

The measures in this article can be automated with a tool

Automate your Next.js / Supabase security controls with the OSS Aegis

Many of the measures here can be mechanically detected and hardened with a single middleware file and static analysis. With the free, MIT-licensed Aegis, you can scan your current project from one command. The vertical risks that need design, I also take on as an audit.

Nhận cả dự án trọn gói lẫn vai trò cố vấn kỹ thuật. Hãy bắt đầu bằng buổi tư vấn miễn phí 30 phút.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Cũng đáng đọc