「フィルター」と「ウォール」の違い
あなたはデータにアクセスできるAIエージェントを構築しています。セキュリティには2つの選択肢があります。データを「フィルター」するか、あるいは「ウォール(壁)」で囲むかです。
フィルターとは、クエリが特定の行のみを返すことを意味します。 ウォールとは、エージェントが隠された行に全く到達できないことを意味します。
これらは、何かが壊れるまでは同じように見えます。
最近、私は120万語をナレッジベースに変換するシステムを構築しました。データの管理にはSupabaseを使用しました。AIエージェントには公開コンテンツのみを見せたいと考えていました。
私は、データをフィルタリングするために標準的なPostgresビューを使用しました。
CREATE VIEW public_seeds AS
SELECT * FROM moments
WHERE visibility = 'public'
AND is_canonical = true;
これは正しく見えます。しかし、重大な欠陥があります。デフォルトでは、Postgresのビューは呼び出し者ではなく、所有者として実行されます。ビューの所有者は多くの場合、フルアクセス権限を持っています。つまり、行レベルセキュリティ(RLS)ポリシーがビューに適用されないのです。
あなたが構築したのはウォールではなく、フィルターです。
フィルターが失敗しても、AIエージェントは気づきません。人間ならエラーや誤ったデータに気づきますが、エージェントは受け取ったものをそのまま処理します。フィルターが失敗すると、エージェントは警告なしにプライベートなデータを使用し始めてしまいます。
Postgres 15は、security_invokerオプションによってこれを解決しました。
security_invokerをtrueに設定すると、ビューは呼び出しロールとして実行されます。これにより、ビューは強制的にRLSポリシーに従うようになります。ビューは構造的なゲート(門)となります。
正しい方法:
CREATE VIEW public_seeds
WITH (security_invoker = true)
AS
SELECT * FROM moments
WHERE visibility = 'public'
AND is_canonical = true;
これでウォールは構造的なものになりました。たとえ開発者が不適切なクエリや結合(join)を書いても、RLSポリシーがテーブルを保護します。
「このようなことは起こるはずがない」は、すべてが完璧に動作することに依存しています。 「このようなことは起こり得ない」は、あなたのアーキテクチャに依存しています。
AI向けに構築する場合、「起こり得ないこと」を想定して設計しなければなりません。
セットアップで確認すべき3つのこと:
- エージェントに
service roleを使用していないことを確認してください。service roleはRLSを完全にバイパスします。 - Postgresのバージョンを確認してください。
security_invokerを使用するにはバージョン15以上が必要です。 - 既存のビューを監査してください。古いビューは、新しいRLSポリシーに自動的には従いません。
Optional learning community: https://t.me/GyaanSetuAi