RLS policies do not authorize on user-editable user_metadata
Why it matters
Supabase Auth stores two metadata blobs in the user's JWT. app_metadata can only be written with the service role. user_metadata (also raw_user_meta_data in the auth.users table) is writable by the user themselves through supabase.auth.updateUser({ data: { role: 'admin' } }). A policy such as using ((auth.jwt() -> 'user_metadata' ->> 'role') = 'admin') therefore lets any signed-in user make themselves an admin with one client call, then read and write everything the policy guards, and the same applies to tenant ids and plan flags. This is the classic AI-generated role check: the model sees a role claim in the token and picks the first metadata key it finds. The advisor flags it as lint 0015. Using app_metadata (set only server-side), a roles table keyed by auth.uid(), or Supabase custom claims via an auth hook is safe.
Severity rationale
Critical because the attacker needs only a normal account and one client call to forge the claim the policy trusts, granting admin or cross-tenant access to everything the policy protects.
Remediation
Authorize on data the user cannot edit. Put roles and tenant ids in app_metadata (set server-side with the service role or an auth hook), or in a table the user cannot write, and read them in policies:
create policy "admins read all" on public.orders
for select to authenticated
using ((select auth.jwt() -> 'app_metadata' ->> 'role') = 'admin');
Replace every user_metadata and raw_user_meta_data reference in a policy or in a function a policy calls. Treat any role previously granted this way as suspect and review who holds it.
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Supabase RLS & Keys audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- cwe:4.14 · CWE-284 — Improper Access Control
- cwe:4.14 · CWE-285 — Improper Authorization
- cwe:4.14 · CWE-639 — Authorization Bypass Through User-Controlled Key
- owasp:2021 · A01 — Broken Access Control
- external · 0015_rls_references_user_metadata — Supabase database advisor lint 0015 RLS references user_metadata
Taxons
History
- 2026-10-03·v1.0.0·Initial supabase-rls-keys v1 authoring; RLS policies (including storage policies and helper functions they call) must not authorize on user_metadata or raw_user_meta_data. Deterministic detect-rg heuristic included.·by supabase-rls-keys-v1-build