トラス構造が幾何学的に組まれた建築物のファサード
技術解説

Supabase RLSは本当に安全か 400行ツールに学ぶ最小権限設計

目次を見る

社内向けの小さなツールをSupabase(認証・DB・APIをまとめて提供するBaaS基盤)で作る機会がある方に向けて、その安全性の見極め方を整理します。個人開発で公開された「Scribe」というドキュメント保存ツールが、Supabaseの行レベルセキュリティだけでユーザーごとのデータ分離を実現していました。これはSRE・インフラの観点からも、最小構成でどこまで安全な基盤を作れるかを考える良い題材です。

Scribeは、AIとの会話ログをMarkdown形式で保存できるツールです。フロントエンドはVanilla HTML/CSS/JS(ライブラリを使わない素のWeb技術)の単一ファイルで、ビルド工程がありません。認証とデータベースはSupabase、ホスティングはNetlifyの無料枠という構成で、コード量は約400行、開発期間は48時間と公開されています。

RLSという仕組みを段階的に理解する

このツールの安全性を支えているのが、PostgreSQLのRow Level Security(行レベルセキュリティ、略してRLS)です。まず前提として、通常のデータベースはテーブル単位でアクセス権限を管理します。

テーブルへの読み書き権限を持つユーザーは、原則としてテーブル内の全行を見られます。アプリ側のコードで「自分のデータだけ表示する」フィルタを書いていても、それはあくまでアプリ層の制御です。

SQLクライアントから直接テーブルにアクセスされたり、アプリのバグでフィルタ条件が漏れたりすれば、他人のデータが見えてしまいます。RLSはこの問題を、データベース自体に権限判定を持たせることで解決します。

公開されているスキーマでは、次のようなポリシーが設定されています。

ALTER TABLE scribe_logs ENABLE ROW LEVEL SECURITY;

CREATE POLICY user_own_logs ON scribe_logs
  FOR SELECT
  USING (auth.uid() = user_id);

このポリシーは「SELECT文を実行したユーザーのIDと、行のuser_idが一致する場合のみ結果を返す」という条件です。アプリ側がどんなクエリを書いても、データベースがこの条件を強制的に適用します。

つまりSQLインジェクション(不正なSQL文を注入する攻撃)が仮に成立しても、RLSが有効であれば他ユーザーの行までは読み取れません。アプリ層とデータベース層で二重に守る「多層防御」の一例といえます。

類似の仕組みとの比較で位置づけを掴む

RLSはPostgreSQL本体の機能で、Supabase固有のものではありません。AWSでいえばDynamoDBのアクセスパターン制御やRDSのIAM認証、GCPならCloud SQLとIAMの組み合わせが近い役割を担います。

ただしSupabaseの特徴は、auth.uid()のようにJWT(JSON Web Token、認証情報を含む署名付きトークン)の中身をSQL関数からそのまま参照できる点です。これにより、フロントエンドから直接データベースを叩く構成でも、サーバーサイドの仲介役を用意せずに認可を実現できます。

日本の開発現場でよく使われるFirebaseのFirestoreセキュリティルールも思想は近く、クライアントからの直接アクセスを前提に、ルール側でアクセス制御を書く点は共通しています。違いはFirestoreがNoSQL・ドキュメント指向であるのに対し、SupabaseはPostgreSQLベースのRDBであり、SQLの表現力とトランザクション整合性をそのまま使える点です。

小規模なSaaSやMVP(実用最小限の製品)を素早く立ち上げたいチームにとって、バックエンドAPIサーバーを自前で書かずに済むのは大きな利点です。一方で、RLSポリシーの設計ミスがそのままデータ漏洩に直結するため、テストの厚みが通常以上に求められます。

読者への影響と今日確認できること

すでにSupabaseを使っている、あるいはこれから採用を検討しているなら、次の点を確認しておくと安心です。

確認ポイント

  • テーブルごとにENABLE ROW LEVEL SECURITYが有効になっているか(Supabaseダッシュボードの「Authentication > Policies」またはTable Editorで確認可能)
  • ポリシーがSELECTだけでなく、INSERT・UPDATE・DELETEにも個別に設定されているか(一つの操作だけ許可漏れがあると危険)
  • auth.uid()が想定通りのユーザーIDを返すか、テストユーザーで実際にクエリを叩いて検証したか
  • RLSを無効化した状態でテーブルを新規作成していないか(Supabaseはデフォルトでは新規テーブルのRLSが無効な場合があるため要注意)

本番運用を見据えるなら、IaC(Infrastructure as Code、インフラ構成をコードで管理する手法)でこのポリシー定義をバージョン管理することも検討に値します。SupabaseはCLIでマイグレーションファイルをSQLとして管理できるため、supabase/migrations配下にポリシー定義を含めておけば、TerraformやPulumiのような外部IaCツールを使わなくても変更履歴を追跡できます。

RLSはアプリ側のバグに関係なく機能する「最後の砦」であり、有効化の確認とポリシーの網羅性チェックは、公開前の必須項目として扱う価値があります。

オブザーバビリティ(システムの内部状態を外部から観測できるようにする設計)の観点では、Supabaseはクエリログやパフォーマンスの可視化機能をダッシュボードで提供しています。無料枠では保持期間や機能が制限されるため、アクセス異常やポリシー違反の試行を継続的に監視したい場合は、有料プランのログ保持期間や外部ログ転送の可否を公式ドキュメントで確認しておくと良いでしょう。

コスト面では、今回のツールがNetlifyとSupabaseの無料枠のみで運用されている点も参考になります。個人開発やPoC(概念実証)段階では、こうした無料枠の組み合わせで十分に安全な基盤を作れることが示された形です。ただし利用者数やデータ量が増えた際の従量課金への切り替わりポイントは、各サービスの料金ページで事前に把握しておくことをおすすめします。

まとめ

Supabaseに限らず、クライアントから直接データベースにアクセスする構成を採用する際は、RLSのようなデータベース側の認可機構が本当に有効かを確認することが出発点になります。

  • 既存テーブルのRLS有効化状況とポリシーの網羅性(SELECT/INSERT/UPDATE/DELETE)を棚卸しする
  • auth.uid()などの認証情報連携を、実際のクエリで動作検証する
  • ポリシー定義をマイグレーションファイルとしてバージョン管理し、変更履歴を追える状態にする
  • 無料枠運用時はログ保持やアラート機能の制限を把握し、監視の抜け漏れを防ぐ

小さな個人開発ツールから学べるセキュリティ設計の原則は、規模の大きいプロダクトにもそのまま応用できます。まずは手元のSupabaseプロジェクトのポリシー一覧を開いて、抜けがないか見直すところから始めてみてはいかがでしょうか。

参考

I Built a Documentation Tool in 48 Hours (While Running a Code Jam)

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。