RLS & tenancy gaps
Row-Level Security enforces membership but leaves several holes a rebuild must close.
-
RLS does not enforce the workflow or role model.
casesallows INSERT/UPDATE/SELECT to any company member (get_user_company_ids()); onlyis_company_adminis blocked from DELETE. No per-role, per-status, per-checklist write restriction. → the entire workflow engine is client-side and bypassable. -
companiesis world-readable. A policy"Public can view companies by slug" FOR SELECT USING (true)was deliberately converted from RESTRICTIVE to PERMISSIVE with noTOrole (migration20260227212620). Anyone, including anon, can read all tenants' company rows — name, slug, branding,union_notification_email. -
Self-provisioning privilege escalation.
companiesINSERTWITH CHECK (true)+ "users create own membership"WITH CHECK (user_id = auth.uid()), but the membershiproleis client-supplied and unconstrained — a user can insert themselves assuper_admin/hr_admin. See self-provision escalation. -
Cross-tenant leaks in the UI queries.
EvidenceVaultqueriesevidence_itemswith nocompany_idfilter ("Biblioteca global de evidência de todos os processos"); the Dashboard's pending-case_gatescount has no company filter either. If RLS on those tables is loose, this is a live cross-tenant read. -
Storage bucket has no tenant isolation — see storage note.
-
App-level role is global-max, not per-tenant — see role note.
Rebuild implication
Enforce the workflow and role model in the database / server (RLS + a transitions
RPC). Scope companies read to a slug-lookup RPC. Constrain self-insert roles (server
assigns the first role, e.g. always super_admin for the creator, never client-chosen).
Add company_id filters everywhere and verify RLS on every table independently.