Tenants & Multi-tenant Control
Workspaces for each law firm. Data is isolated at the database layer via Supabase Row Level Security.
Total tenants
5
Active users
94
Combined AI spend
$23,272
Avg compliance
87%
Supabase RLS Tenant Isolation
How tenant data leakage is prevented at the database layer.
Supabase RLS Tenant Isolation
Every tenant-scoped table carries a tenant_id column. Row Level Security policies bind reads and writes to the tenant_id claim inside the session JWT — Postgres enforces isolation at the row level, not the application layer.
auth.jwt() ->> 'tenant_id'
tenant_isolation (USING + WITH CHECK)
FORCE ROW LEVEL SECURITY
cross-tenant rows filtered to 0
-- Enable row level security on every tenant-scoped table
ALTER TABLE legal_posts ENABLE ROW LEVEL SECURITY;
-- Read + write isolation keyed on the JWT tenant claim
CREATE POLICY "tenant_isolation"
ON legal_posts
USING (tenant_id = auth.jwt() ->> 'tenant_id')
WITH CHECK (tenant_id = auth.jwt() ->> 'tenant_id');
-- Force RLS even for table owners (defense in depth)
ALTER TABLE legal_posts FORCE ROW LEVEL SECURITY;Allowed vs. blocked queries
SELECT * FROM legal_posts WHERE tenant_id = 'tnt_hamilton_reed'; -- session JWT tenant_id = 'tnt_hamilton_reed'
tenant_id in row matches auth.jwt() ->> 'tenant_id'. RLS USING clause passes.
SELECT * FROM legal_posts WHERE tenant_id = 'tnt_westbridge'; -- session JWT tenant_id = 'tnt_hamilton_reed'
Row tenant_id ≠ JWT tenant_id. Postgres returns 0 rows — silently filtered by RLS, no error leakage.
SET LOCAL role = 'service_role'; SELECT tenant_id, SUM(cost) FROM ai_traces GROUP BY tenant_id;
service_role bypasses RLS by design. Used only inside trusted BullMQ workers, never exposed to the client.
UPDATE legal_posts SET status = 'published' WHERE id = 'post_9912'; -- attacker JWT tenant_id = 'tnt_attacker'
WITH CHECK + USING both enforce tenant_id equality. Update affects 0 rows; attempt recorded in audit log.
Cross-tenant access attempt blocked by RLS policy.
JWT tenant_id=tnt_attacker attempted to read tnt_silva_partners.legal_posts. Postgres returned 0 rows; event recorded in the audit log with risk level critical.