Security overview
What we do to keep your data safe.
Tenant isolation
Every table that holds firm data carries a tenant_id column and Row-Level Security is forced on. Reads and writes go through the tenant client which sends an x-tenant-id header; RLS policies compare that header to the caller's membership before returning a row. A lawyer at Firm A cannot read Firm B's data even by directly hitting our API. Cross-tenant refusal is covered by isolation tests under packages/testing/src/rls/.
Authentication
Firm users sign in with a magic link sent by Supabase Auth. Sessions live in HTTP-only cookies. Public recap and wizard links use single-use, time-boxed tokens; a revoked token cannot be reused.
AI boundaries
AI can suggest, extract, and rerank; AI cannot conclude, verify, or satisfy a requirement. Every AI-produced fact is stored with state = 'ai_suggested' and database triggers refuse to link it to a satisfied requirement until a human verifies it. See PLAN.md §10.
Audit trail
public.audit_events is append-only. Every user action, every server action, and every state change stamps an event with the actor, tenant, matter, action, and metadata. The trail powers the Timeline tab on every matter and is readable by platform admins for investigation.
Transport and headers
- HTTPS everywhere. HSTS enabled in production (1-year max-age, includeSubDomains).
- x-frame-options: DENY. x-content-type-options: nosniff. referrer-policy: strict-origin-when-cross-origin.
- permissions-policy: camera=(), microphone=(), geolocation=(), payment=().
Backups
Supabase maintains daily point-in-time backups of the database and versioned storage for uploads. Firms can also export their entire matter as JSON at any time from the matter overview.
Reporting a vulnerability
Email security@immigrationsuite.example with details. Please do not exploit findings against live tenant data. We respond within two business days and coordinate disclosure timelines.