---
title: "Supabase security definer view：RLS迂回の理由"
description: "Supabaseのビューが、テーブルではRLSが隠す行を返す理由、Advisorの警告security_definer_viewの意味、そして直す設定を、ローカルでの再現とともに紹介します。"
url: "https://madsalt.tech/ja/notes/supabase-security-definer-view/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/ja/about/"
language: "ja"
published: 2026-11-02
---

# Supabase security definer view：RLS迂回の理由

TL;DR

Postgresのビューは、`security_invoker = true`を付けて作らない限り、所有者の権限で実行されます。Supabaseでは所有者は通常`postgres`で、RLSを迂回します。そのため、保護されたテーブルの上に作ったビューは、テーブル自体が隠す行を返します。私のローカルスタックでは、公開キーはテーブルから0行、その上の通常のビューから3行中3行を取得しました。`security_invoker = true`を付けると、ビューも0行を返しました。Postgres 15以降では、公開スキーマのすべてのビューに設定してください。

私の無料の[Supabaseセキュリティチェッカー](https://madsalt.tech/ja/tools/supabase-security-check/)には「RLS を迂回するビュー」というルールがあり、SupabaseのSecurity Advisorは同じ問題を`security_definer_view`として報告します。漏れ元のテーブルは保護されているように見えるので、見落としやすい問題です。

## Supabaseのビューはなぜ RLS を迂回する？

ビューは保存されたクエリです。デフォルトでは、Postgresはビューの中のテーブルの権限とRLSを、呼び出した人ではなくビューの所有者として確認します。Supabaseの[RLSガイド](https://supabase.com/docs/guides/database/postgres/row-level-security)ははっきり書いています。ビューは通常`postgres`ユーザーで作られるため、デフォルトではRLSを迂回し、保護されたテーブルの上のビューは、ポリシーが止めるはずだったすべての行を渡してしまいます。

Data APIはビューをテーブルと同じように提供します。ビューが公開スキーマにあり、`anon`や`authenticated`がそれにselectできるなら、公開キーで読めます。

## 私のテストでビューは何を返した？

ローカルのSupabaseスタック（CLI 2.109.1、Postgres 17.6、PostgREST 14.14）で、架空の顧客3人を入れたテーブル`orders`を作り、ポリシーなしでRLSを有効にしました。その上に、`anon`への`select`のgrant付きでビュー`order_summary`を作りました。そして、publishable keyで両方を呼び出しました。

![同じテーブルを読む3つの方法。ローカルのSupabase、RLS有効でポリシーなし、publishable key。テーブル自体：Publishable key → orders、RLS有効 → 0行、その上の通常のビュー：Publishable key → order_summaryビュー → 3行中3行、security_invokerのビュー：Publishable key → security_invokerビュー → 0行。](https://madsalt.tech/charts/ja/supabase-security-definer-view.svg)

*ローカルのSupabaseスタックでの私の再現、2026年10月23日。*

| publishable keyでのリクエスト                                     | 結果                    |
| ----------------------------------------------------------------- | ----------------------- |
| `GET /rest/v1/orders`（RLS有効、ポリシーなし）                    | `[]`                    |
| `GET /rest/v1/order_summary`（通常のビュー）                      | 名前とメール付きの全3行 |
| `security_invoker = true`にしたあとの`GET /rest/v1/order_summary` | `[]`                    |

テーブルも同じ、キーも同じです。返ってくるものを変えたのは、ビューの設定だけでした。

## どう直す？

Postgres 15以降（現在のSupabaseプロジェクトはすべてこれです）では、ビューが呼び出し元としてRLSを確認するようにします。

```sql
alter view public.order_summary set (security_invoker = true);
```

新しいビューなら、定義に含めます。

```sql
create view public.order_summary
with (security_invoker = true)
as select customer, email, total from public.orders;
```

こうすると、呼び出し元は元のテーブルに対する`select`も必要になり、どの行を返すかはテーブルのポリシーが決めます。公開の件数のように、呼び出し元が行単位では見られないデータをビューで集計する必要がある場合は、公開スキーマの外に置き、結果を絞った関数を通して提供してください。

`public`にある、まだ所有者として実行されているビューを一覧にするには、次のクエリを使います。

```sql
select c.relname as view
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'v'
  and not coalesce(
    (select bool_or(option = 'security_invoker=true' or option = 'security_invoker=on')
     from unnest(c.reloptions) as option), false);
```

私のスタックでは、通常のビュー1つと`security_invoker = true`のビュー2つがある状態で、通常のものだけが返りました。

マテリアライズドビューはRLSにまったく対応していないため、チェッカーは別のルール「API に公開されたマテリアライズドビューまたは外部テーブル」で指摘します。secret keyから`SECURITY DEFINER`関数まで、ほかにポリシーをすり抜けるものは[Lovableアプリのセキュリティチェックリスト](https://madsalt.tech/ja/blog/lovable-app-security-checklist/)にあり、ポリシー自体を証明する方法は[Supabase RLSのテスト方法](https://madsalt.tech/ja/blog/how-to-test-supabase-rls/)にあります。ヘルパー関数でチームを引き、再帰を起こさないポリシーは、[Supabase RLSポリシー例：APIで検証済み](https://madsalt.tech/ja/blog/supabase-rls-policy-examples/)にあります。security definer関数にはもう1つ、固定された`search_path`という設定が必要です。私のテストで`search_path = public`では足りなかった理由は、[Supabase search_path警告：意味と対処](https://madsalt.tech/ja/notes/supabase-function-search-path-mutable/)にあります。
