Access rules that let the wrong person in
10 verified cases
- What it looks like
- The app works when you test it alone. Then someone finds that any visitor, or any signed-in user, can read, change or delete other customers' rows. That includes sessions, credentials and password hashes. In some cases a user makes themselves an admin at signup.
- Why it happens
- Row-level security is off on a table the browser can reach, or a policy checks only that someone is signed in, not that they own the row. The public key ships in the bundle, so every exposed table becomes a public API. Roles are read from signup metadata the user controls, and client-callable functions that run with elevated rights skip the policy completely.
- The fix
- Turn on RLS for every table in an exposed schema and deny by default. Write each policy against ownership (auth.uid() = owner_id), and write it separately for select, insert, update and delete. Never authorise from user-editable metadata; keep roles in a table only the server writes. Audit every function that runs with definer rights. Test from outside the app as the anonymous key, as user A and as user B.
- With Gemmein
- Each collection has exactly one of seven fixed, plain-English rules (private, shared, public_read, community, addressed, direct, admin_write), enforced on Gemmein's servers. The app never implements authorization, and every session reports `member`, the owner's included, so there is no role to escalate. Tenant isolation is on by default. Sign-in uses email codes, and no password database exists. The MCP server's `check_integration` runs live isolation checks against dev, aimed at the bug that passes with one user and breaks with two.