RLSとアプリ側の絞り込み
RLS(行レベルセキュリティ)を有効にしたテーブルに対して、 アプリ側のクエリでも同じ条件を書くと、条件は AND で重なる。 DB側を緩めてもアプリ側が厳しければ、見える範囲は広がらない。
何が起きるか
厄介なのは、権限エラーにならず0件が返ること。 アプリからは「データを持っていないユーザー」と区別がつかないので、 画面は例外を出さずに空を表示する。ログにも何も出ない。
指針
| 場面 | 書き方 |
|---|---|
| 可視範囲の判定 | RLSに一本化する。アプリ側に同じ条件を書かない |
| レコードの作成 | 作成者カラムは入れてよい(記録用) |
| 作成者カラムでの絞り込み | 「作成者だけに見せる」が仕様のときだけ |
| どうしても両方に書く場合 | どちらが正本かをコードコメントに書く |
作成者カラムは「絞り込みの条件」と「記録としての属性」を兼ねてしまいやすい。 役割を分けて考えると、絞り込みから外しても作成者は保持できる。
テストの置き方
RLSの緩め忘れ・締め忘れは、テストの接続方法を間違えると原理的に検出できない。
| 接続 | RLS | この種のバグ |
|---|---|---|
| service_role キー | 迂回する | 検出できない |
| クライアントをモック | そもそも通らない | 検出できない |
| 実ユーザーのJWT(anonキー) | 適用される | 検出できる |
実ユーザーのJWTで接続し、本物のポリシーを通す統合テストを書く。 可視範囲を変えたときは、次の3点をセットで固定する。
- 広げた側が見えること — 修正の対象そのもの
- 見えてはいけないものが返らないこと — 緩めすぎの検出。これを忘れると穴が開く
- 既存の役割の見え方が変わらないこと — デグレの検出
2番目を省くと「見えるようにする」修正が「全員に見える」修正になっていても通ってしまう。
開発用シードに「弾かれる人」を置く
権限が足りないユーザーが手元に居ないと、403の表示確認は後回しになり続ける。 シードスクリプトに、権限カラムが null のメンバーや閲覧専用ロールを常駐させておく。 画面を1本作るたびに、正常系と拒否系の両方をその場で見られる状態にしておくのが安い。
実際に踏んだ経緯
ちょうぼっちで、companies のRLSを「作成者のみ」から「組織メンバー」へ広げた後も、
クライアント側の会社解決に .eq("user_id", userId) が残っていた。
招待した accountant / viewer は全画面が「会社情報が見つかりません」になった。
経緯は RLSを広げたのに、アプリ側の絞り込みが残っていた に書いた。