UPDATE policy missing WITH CHECK
Users may edit their own row, and nothing stops them changing its role, plan or owner.
- Severity
- Medium
- Who can exploit it
- Signed-in users, on rows they can already edit
- Found by
- Free RLS check (static)
- Lab proof
- Write test included for your engineer
What it is
An UPDATE policy answers two questions. USING decides which rows you may change. WITH CHECK decides what a row may look like afterwards. Leave out WITH CHECK and Postgres reuses the USING rule for the new row.
That fallback is safe for the owner column when USING is a strict ownership test. It is not enough when the row carries columns the user should never set themselves (role, plan, credits, is_verified), or when USING is broader than what a changed row should be allowed to be. Then a user edits their own row and makes themselves an admin.
Example of vulnerable code
Simplified from the patterns we see in AI-built apps. Synthetic: not taken from any real app.
create table public.profiles (
id uuid primary key references auth.users (id) on delete cascade,
full_name text,
role text not null default 'member'
);
create policy "Users can update own profile"
on public.profiles for update
to authenticated
using ((select auth.uid()) = id);
-- Any user can now run, from the browser:
-- update profiles set role = 'admin' where id = auth.uid();
Check it yourself
-- 1. UPDATE (and ALL) policies with no WITH CHECK
select tablename, policyname, cmd, roles, qual
from pg_policies
where schemaname = 'public'
and cmd in ('UPDATE', 'ALL')
and with_check is null;
-- 2. Which columns can signed-in users change?
select table_name, column_name
from information_schema.column_privileges
where table_schema = 'public'
and grantee = 'authenticated'
and privilege_type = 'UPDATE'
order by table_name, column_name;
The fix PatchLabs writes
PatchLabs makes the rule explicit and limits which columns a user may change. The column grant is what actually stops the role change; WITH CHECK stops a row being handed to someone else.
drop policy "Users can update own profile" on public.profiles;
create policy "Users can update own profile"
on public.profiles for update
to authenticated
using ((select auth.uid()) = id)
with check ((select auth.uid()) = id);
-- Users may change these columns and no others
revoke update on public.profiles from authenticated;
grant update (full_name) on public.profiles to authenticated;
Every fix arrives as a migration file (plus any code change it needs, noted in the report) for your engineer to approve. Download it, or commit it on GitHub in one click from the report page. Automatic pull requests need our GitHub App, which is coming.
The proof it runs
[TEST] owner set full_name → 1 row OK owner set role='admin' → permission denied DENIED stranger set full_name on owner's row → 0 rows DENIED
Be clear about what is automatic. The lab's three-identity proof covers reads on every check. For write findings like this one, the report includes the test above as SQL for your engineer to run in the lab or a branch database; it is not yet part of the automatic proof.
begin;
set local role authenticated;
select set_config('request.jwt.claims',
'{"sub":"<your user id>","role":"authenticated"}', true);
update public.profiles set role = 'admin' where id = '<your user id>';
-- before the fix: UPDATE 1 · after: permission denied for table profiles
rollback;
When the pattern is intended
A missing WITH CHECK is harmless when USING is a strict ownership test and every column on the row is one the user is allowed to set. Many tables are like that, so treat this as a prompt to look, not proof of a hole. The report says which columns signed-in users can change, so you can decide quickly.
Related findings
Always-true policy
A policy that says yes to everyone. On writes, anyone can change or delete your data.
PL-002Any signed-in user can read every row
Signing up unlocks other people's rows. If sign-up is open, that means anyone.
PL-006SECURITY DEFINER function exposed
A function that runs with the owner's powers, callable by anyone through the API.
Guides
Free check
Does your app have PL-004?
Paste your public repo. We rebuild the database from your migrations, test it as a visitor, a stranger and the owner, and hand you the proven fix.
Last reviewed 10 Nov 2026. Finding IDs are stable: link to them freely. Spotted a mistake? Tell us.