TL;DR
| 句 | 役割 | 効く |
|---|---|---|
USING | 既存行 | SELECT / UPDATE / DELETE |
WITH CHECK | 書き込み後の | INSERT / UPDATE |
-- 危険: USING がないと全行が UPDATE の標的になる
create policy "update own profile"
on profiles for update
to authenticated
with check ( (select auth.uid()) = user_id );
-- 正解: UPDATE は USING + WITH CHECK を両方書く
create policy "update own profile"
on profiles for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
USING と WITH CHECK、何が違うのか
Supabase の
If no
with checkexpression is defined, then theusingexpression will be used both to determine which rows are visible and which new rows will be allowed to be added.
PostgreSQL の
- USING: 「この
行を 操作させていいか」。 SELECT では 返す行を 絞り、 UPDATE では 更新対象を 絞り、 DELETE では 削除対象を 絞る。 - WITH CHECK: 「この
行を 書き込んでいいか」。 INSERT では 挿入行を、 UPDATE では 更新後の 行を 検査する。
UPDATE は
危険パターン①: UPDATE ポリシーに USING がない
これがWITH CHECK だけ
-- これは穴がある
create policy "users can update own profile"
on profiles for update
to authenticated
with check ( (select auth.uid()) = user_id );
USING 句が
- User A(uid =
'aaaa-...')が User B の行( id = 42,user_id = 'bbbb-...')を標的に する UPDATE profiles SET user_id = 'aaaa-...', display_name = 'hijacked' WHERE id = 42を実行 USINGチェック: 定義なし → スルーWITH CHECK:(select auth.uid()) = user_id→'aaaa-...' = 'aaaa-...'→ 通過- 結果: User B の
行が User A の 所有に 書き換わる
WITH CHECK がUSING が
危険パターン②: FOR ALL に WITH CHECK がない(暗黙フォールバック)
Supabase の Dashboard が
-- 動くが意図が不明確
create policy "users own data"
on profiles for all
using ( (select auth.uid()) = user_id );
-- WITH CHECK 省略 → PostgreSQL が USING 式を WITH CHECK に流用
WITH CHECK をUSING 式を WITH CHECK としてもuser_id ≠ auth.uid() な
ただし暗黙のFOR ALL でも
-- 意図を明示する
create policy "users own data"
on profiles for all
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
コマンド別の正しい書き方
-- SELECT: USING のみ
create policy "read own profile"
on profiles for select
to authenticated
using ( (select auth.uid()) = user_id );
-- INSERT: WITH CHECK のみ
create policy "insert own profile"
on profiles for insert
to authenticated
with check ( (select auth.uid()) = user_id );
-- UPDATE: USING + WITH CHECK 両方
create policy "update own profile"
on profiles for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
-- DELETE: USING のみ
create policy "delete own profile"
on profiles for delete
to authenticated
using ( (select auth.uid()) = user_id );
Supabase 公式は「UPDATE 操作を
なお (select auth.uid()) と () で
現行ポリシーの棚卸し
pg_policies ビューで USING / WITH CHECK の
select
tablename,
policyname,
cmd,
qual as using_expr,
with_check as with_check_expr
from pg_policies
where schemaname = 'public'
order by tablename, cmd;
cmd = 'UPDATE' のusing_expr が NULL なら危険using_expr が NULL のものが 2 つありました。
修正は drop してから create がCREATE OR REPLACE POLICY は
drop policy if exists "users can update own profile" on profiles;
create policy "users can update own profile"
on profiles for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
まとめ
- USING = 既存行
フィルタ。 SELECT・ UPDATE(対象選択)・ DELETE に 効く - WITH CHECK = 書き込み検証。
INSERT・ UPDATE(結果 チェック)に 効く - UPDATE ポリシーに USING がないと全行が UPDATE の
標的に なり、 データ 乗っ取りが 通る pg_policiesでusing_expr IS NULLな UPDATE ポリシーを確認して 修正する - 暗黙の
フォールバックに 頼らず、 FOR ALLでも USING + WITH CHECK を明示する