Skip to content

profiles RLS lets a signed in user overwrite their own xp, level, and role directly #397

Description

@jakharmonika364

What happened?

The profiles_update_self policy (supabase/migrations/0001_initial_schema.sql, line 277) is:

create policy profiles_update_self on profiles for update
  using (auth.uid() = id);

This only restricts which row you can update, not which columns. Combined with the table grant authenticated gets in migration 0021 (grant select, insert, update, delete on all tables in schema public to authenticated), any signed in user can call the Supabase client directly from the browser, bypassing the app's own server actions in profile.ts entirely, and run something like:

update profiles set xp = 999999, level = 5, role = 'maintainer' where id = auth.uid()

There is no trigger or check constraint stopping writes to xp, level, role, or audit_completed.

Steps to Reproduce

  1. Sign in as any user
  2. Using the browser's own Supabase client and the user's session, call .from('profiles').update({ xp: 999999, level: 5 }).eq('id', <own id>)
  3. The update succeeds with no server side validation at all

Expected Behavior

Sensitive columns like xp, level, and role should only be writable through server side logic (XP events, audits, admin actions), not via direct client updates, regardless of which row they target.

Where does this occur?

API / Database (RLS)

Additional Context

The simplest fix is probably a before update trigger on profiles that resets xp, level, role, and audit_completed back to their old values unless the request comes from the service role, since RLS alone can't cleanly express "this column only, only under these conditions" once a role already has broad table access.

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions