Scope of this API vs. production RLS state. This endpoint (and the
RLS_TABLES registry behind it, src/lib/db/rls.ts) only covers
Prisma-modeled tables that were registered in that array before 2026-07-10.
As of 2026-07-10, RLS is enabled and forced on all 111 target tables in
production — this endpoint’s registry plus 51 additional tables (e.g.
consent_records, webauthn_credentials, shopify_shops,
tenant_settings) that were enabled directly via
prisma/manual-sql/35_rls_track_a.sql and are not tracked by this API.
Tables with a tenantId/tenant_id column got the standard
tenant_isolation policy described below; tables without one (e.g.
NextAuth tables, child tables FK’d to a tenant-scoped parent) got
default-deny RLS instead. Supabase’s Data API (the anon/PostgREST HTTP
surface that could otherwise reach any of these tables directly,
bypassing the app) has also been disabled at the source.This does not mean RLS governs the app’s own queries. The app’s
Postgres connection uses a BYPASSRLS-class role by design, so none of
these policies constrain what the application itself can read or write —
they only stop the anon/PostgREST path and any other non-bypass
connection. Tenant isolation for the app’s own traffic is still enforced
at the application layer (the where: { tenantId } convention, backed by
isolation tests) via requireTenant(). withTenantRLS() (src/lib/db/rls.ts)
exists as an opt-in helper for scoping a transaction’s session variable,
but it has no production call sites yet — app-query-level RLS enforcement
is a separate, deliberately-not-yet-executed initiative.
GET /api/v1/admin/rls
Check RLS status on the tables tracked by this API (RLS_TABLES in
src/lib/db/rls.ts). Returns which of those tables have RLS enabled,
forced, and which have the tenant_isolation policy. This is a subset of
all RLS-protected tables in production — see the scope note above.
Response
Summary Fields
| Field | Type | Description |
|---|
totalTables | integer | Total number of tenant-scoped tables |
rlsEnabled | integer | Tables with RLS enabled |
rlsForced | integer | Tables with FORCE RLS (applies even to table owners) |
withPolicy | integer | Tables with the tenant_isolation policy |
missingRLS | string[] | Table names missing RLS or the isolation policy |
Roles
admin only
POST /api/v1/admin/rls
Enable RLS on all tables tracked by this API (RLS_TABLES). This is idempotent and safe to call multiple times. For each table, it:
-
Enables RLS (
ALTER TABLE ... ENABLE ROW LEVEL SECURITY)
-
Forces RLS for table owners (
ALTER TABLE ... FORCE ROW LEVEL SECURITY)
-
Creates a
tenant_isolation policy scoped to the session variable app.current_tenant_id:
The current_setting(...) call is wrapped in a scalar subquery so
Postgres evaluates it once per query (an InitPlan) instead of once per
row. current_setting(..., true) returns NULL when the variable isn’t
set, and NULL never equals a tenant ID — so a connection with no tenant
context set sees zero rows (fail-safe), and the WITH CHECK clause
applies the same rule to inserts/updates.
Response
Response Fields
| Field | Type | Description |
|---|
success | boolean | true if no tables failed |
enabled | string[] | Tables where RLS was successfully enabled |
failed | string[] | Tables where RLS enablement failed |
totalTables | integer | Total number of tenant-scoped tables |
An audit log entry is created for the operation.
Roles
admin only
RLS enforcement depends on the PostgreSQL session variable app.current_tenant_id being set on a connection (via set_config('app.current_tenant_id', ..., true)) before that connection’s queries can be tenant-scoped by these policies. The platform’s own Prisma client does not set this variable — it connects with a BYPASSRLS-class role, so these policies don’t constrain it either way. This session variable only matters for connections that are both non-bypass and have RLS enabled/forced on the tables they query (e.g. the anon/PostgREST path, or a future non-bypass role under Track B). Tenant isolation for the app’s own queries is enforced by the application-layer where: { tenantId } convention, not by this session variable.
See also: Admin | Security