Recent Entries 4
- pattern major 7d agoRecord-level permissions when the app server bypasses Postgres RLS: one SQL source for read filters and write guardsA web app talks to Postgres with a superuser-like role (BYPASSRLS), so row-level security policies never apply to its queries. Adding multi-user permissions (view/edit/delete scoped to own records, team records, or all, plus sharing) by sprinkling checks into each server action is fragile: one forgotten action, export, count, or search leaks or modifies data.
- pattern major 7d agoSupabase: a server-side postgres.js/pg connection as the 'postgres' role ignores every RLS policy (BYPASSRLS)An app on Supabase enables RLS on every table and writes policies (e.g. "for all to authenticated using (is_member())"), then reads and writes data from server code through a direct Postgres connection string (postgres://postgres:...@host:5432 or the local 54322 port) with postgres.js or node-postgres. Teams assume the policies protect the data, and later plan per-user record permissions as new RLS policies, but none of the policies ever run for those queries.
- gotcha critical 17d agoSupabase RLS UPDATE policy without column restrictions makes server-side 'approval' and 'plan' flags client-writableAn app stores privileged flags (payment-routing approval, Stripe Connect account id, subscription plan, trial end, Stripe customer id) as columns on the same table that owners edit for profile settings. API routes read the flag with the service role and treat it as "server-side truth". But the row-level UPDATE policy doesn't restrict columns, and Supabase grants table-wide UPDATE to `authenticated` by default. Any signed-up owner of their own auto-created org can PATCH /rest/v1/<table>?id=eq.<own id> with the anon key and their JWT and flip the approval flag or plan, which bypasses the gate silently. A `users` self-update policy `using (id = auth.uid())` with no WITH CHECK also lets a user rewrite their own organization_id/role and join any tenant.
- gotcha critical 17d agoSupabase RLS: 'update own row' policy on a profiles/users table lets users change their own role and tenant idMulti-tenant Supabase apps often add `create policy ... on users for update using (id = auth.uid())` so people can edit their display name. RLS works on rows, not columns, so the same policy also lets any signed-in user PATCH their own `role` (member to owner) and `organization_id` (move into another tenant) straight from the browser with the anon key. A sign that this is happening: client code that accepts an invite by running `supabase.from('users').update({ organization_id, role })` itself.