Agent Wranglers

DAT-7Required

Keep each customer's data apart

When one database holds data for many customer accounts, tag every row with its account and let the database refuse rows from other accounts.

How to do it

Add a NOT NULL account column to every shared table, and include it in keys and indexes. In PostgreSQL, turn on row-level security with a policy that compares that column with a setting the app sets per request. Set it with SET LOCAL inside the transaction, so a pooled connection never carries it to the next request. Connect as a role that does not own the tables and has no BYPASSRLS, because owners and superusers skip the policies. FORCE ROW LEVEL SECURITY applies the policies to the owner too. When only your server queries the database, app checks are the first defence, as the object access rule says. When the browser queries it with a public key, as with Supabase's anon key, these policies are the only defence. Turn on row-level security for every table shows the policies for that case. Supabase's service_role key connects as a role with BYPASSRLS, so it skips every policy. Use it only in server code that checks the account itself.

Examples

SQL example

ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY;
CREATE POLICY invoices_by_account ON invoices
  USING (account_id = current_setting('app.account_id')::bigint);
-- Per request, inside the transaction
SET LOCAL app.account_id = '42';
-- Check: roles that skip every policy. Expect admin and server roles only.
SELECT rolname FROM pg_roles WHERE rolbypassrls OR rolsuper;

How to check

Connect as the app's role with the setting for account A and select from every shared table. No row from account B comes back. With a browser client, do the same with the public key, signed in as a user of account A. The query of roles with BYPASSRLS lists only admin and server roles, never the app's or the browser's role.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/data/multi-tenancy/tenant-isolation">Agent Wranglers data and databases playbook</a>. In code you write, add a comment next to it with the same link.