Organizations and tenancy¶
Two different things share the phrase 'row-level security'
This page is about PgQueryNarrative's own multi-organization isolation of
its metadata. It is unrelated to the demo-data RLS walkthrough,
which demonstrates PostgreSQL RLS on the demo.sales analytical table using a
role the application never connects as. Do not read one as documentation for the
other.
What is isolated, and how¶
PgQueryNarrative's tenancy unit is the organization. Every metadata table that
holds organization-scoped data (investigations, their candidate history and
linked regression alerts), reports, saved queries, schedules, regression snapshots
and alerts, and more has row-level security enabled and forced. That includes the identity
tables organization_members and oidc_group_org_mappings and the audit writer's
audit_log_buffer (migration 000060). Login resolves an identity before an organization
is chosen, so those two tables have one narrow, read-only exception each: a user's own
memberships in every organization, and the mappings for the groups in the token. Each is a SELECT
policy only: an INSERT, UPDATE or DELETE can reach a row only inside the current organization. Only tables that hold no organization-scoped data have none:
organizations (the list itself), api_key_usage, oidc_pkce_states and
rate_limit_buckets:
USING (organization_id::text = NULLIF(current_setting('app.current_org_id', true), ''))
current_setting(..., true) returns NULL when the GUC is unset, so an
unauthenticated or misconfigured request context sees no rows, not every
organization's rows. The application sets app.current_org_id with
set_config(..., is_local := true) at the start of every transaction that touches
app.*, scoped to that transaction only.
The app role is NOBYPASSRLS, so these policies apply even to the server's own
connections; isolation does not depend on the application "remembering" to filter
by organization, though services also filter explicitly by organization_id as a
second layer.
Background jobs¶
Scheduled work (the schedule runner, webhook retry, EXPLAIN snapshot retention) runs
across every organization's data, so it uses a separate app.scheduler_bypass
setting that specific policies are written to honour, rather than running as a
different, RLS-exempt role.
Analytical database separation¶
Isolation on the analytical side is a different mechanism entirely: there is no RLS requirement for your own database:
- Connection assignment: an organization can only use connections explicitly
assigned to it (or, if
SECURITY_CONNECTION_ALLOWLIST_REQUIREDis off and the organization has no assignments at all, every configured connection). - Per-connection actions:
query,explain,analyze,schema,report,schedule,stats,ask, granted per (organization, connection). See Multiple connections. -
Per-organization secrets:
/admin/connection-secretslets an organization supply its own connection DSN (encrypted at rest, see Data handling), so two organizations can point the same connection id at genuinely different databases. -
Statement statistics:
pg_stat_statementsis kept per database role, not per organization. On a connection several organizations share (one read-only role), the SQL text of one organization's analysts would appear in every other organization'sGET /queries/stats. When more than one organization exists, only a platform administrator may read it on a shared role (STAT_STATEMENTS_SHARED); an organization with its own credentials sees only its own role's statements. The regression poller skips such a connection for the same reason (it would copy the other organizations' SQL text into this one's tables), and the two workload totals on the workspace overview read as zero for non-platform users there.
An organization with no assignment for a connection, under
SECURITY_CONNECTION_ALLOWLIST_REQUIRED (on by default in production), gets 400
CONNECTION_FORBIDDEN rather than silent access.
What this is not¶
PgQueryNarrative is explicitly not a public multi-tenant SaaS product, see Trust model. Organization isolation exists to let one internal deployment serve multiple teams without their investigations, reports and saved queries leaking into each other, not to safely host untrusted third parties.
See also¶
Authentication and roles · Database roles · Multiple connections · Demo-data RLS