This page is maintained by Viewli to answer common questions about how tenant data is isolated in the Viewli backend. It describes the app's current controls; it is not an independent certification.

Security & tenant isolation

Viewli is a multi-tenant application. Every customer workspace is a tenant, and all business data — screens, playlists, schedules, rules, analytics, audit trails — is scoped to a tenant_id. Isolation is enforced in the database with Postgres Row-Level Security (RLS), not in application code, so a compromised or buggy client cannot bypass it.

Membership model

Core RLS helpers

All tenant-scoped policies delegate to a small set of stable SECURITY DEFINER functions. Policies never inline joins to user_roles or tenant_members, which avoids recursive RLS and keeps predicates cheap.

Required policy shape for tenant tables

Every table that stores tenant-owned data must follow the same four rules. New migrations that violate any of them are considered broken.

  1. tenant_id uuid NOT NULL — no nullable tenant column, ever. A nullable tenant_id combined with an IS NULL branch in a policy is a cross-tenant bypass.
  2. ENABLE ROW LEVEL SECURITY on the table, plus explicit GRANTs to authenticated (and service_role for admin/edge paths). No blanket anon grants on tenant tables.
  3. A single FOR ALL policy scoped to authenticated users with both USING and WITH CHECK set to is_tenant_member(auth.uid(), tenant_id). Same predicate on both sides prevents a user from moving a row into another tenant on UPDATE.
  4. For admin-only mutations (billing, member removal, role changes) add a second policy gated on has_role(..., 'tenant_admin') or is_system_admin(auth.uid()).

Audit events

audit_events is append-only from the client's perspective. It intentionally has no client INSERT policy — writes only happen through a server-side RPC so the actor, tenant, and payload cannot be forged.

Tenant lifecycle RPCs

Membership and role changes go through SECURITY DEFINER RPCs so authorization is checked once, in the database:

Regression testing

The invariants above are covered by an in-database regression suite (run_tenant_isolation_regression) that impersonates two authenticated users and an anonymous session, then asserts:

Any future migration that reopens a null-tenant bypass, drops the NOT NULL on tenant_id, or re-adds a client INSERT path on audit_events will fail this suite before shipping.

Reporting a vulnerability

If you believe you've found a security issue in Viewli, email security@viewli.co.uk with a description and reproduction steps. Please give us a reasonable window to investigate and remediate before public disclosure.