Security Fix: Row-Level Security Now Covers All Tenant Tables
Security Fix: Row-Level Security Now Covers All Tenant Tables
Version: 1.1.0
Summary
Version 1.1.0 resolves a critical security gap in which database-level Row-Level Security (RLS) policies were only enforced on 5 of approximately 25 tenant-scoped tables. High-value tables — including those storing HMRC credentials, OAuth tokens, financial transactions, and property data — were not covered.
This release extends RLS enforcement to all tables that carry an org_id column, closing the gap at the PostgreSQL layer.
Background: What Is Row-Level Security?
Row-Level Security is a PostgreSQL feature that restricts which rows a database session can read or write, based on a policy evaluated for every query. In this platform's multi-tenant architecture, each database session sets an org_id context variable via setOrgContext, and RLS policies ensure that queries automatically filter to rows belonging to that organisation — regardless of what SQL the application layer sends.
This means that even if application code contains a bug, an ORM misconfiguration, or a SQL injection vulnerability, the database itself will refuse to return another tenant's rows.
What Was the Problem?
The RLS_TABLES list in src/db/rls.ts previously included only:
org_members
subscriptions
usage_events
api_keys
audit_log
The remaining ~20 tables — including some of the most sensitive in the system — had no database-level tenant isolation. Any query reaching those tables relied entirely on application-layer filtering (e.g. a WHERE org_id = ? clause in ORM code). A single missed clause, a raw query, or an injected payload could have returned rows across tenant boundaries.
Highest-risk tables that were unprotected:
hmrc_credentials— stored HMRC login credentialshmrc_tokens— HMRC OAuth access and refresh tokenstransactions— full financial transaction historyquarterly_summaries— completed and in-progress HMRC quarterly submissionsbank_connections/bank_accounts/bank_transactions— open banking dataproperties— landlord property portfolioagentos_connectionsand related tables — AgentOS integration data
What Changed in v1.1.0?
1. RLS_TABLES Extended
src/db/rls.ts now includes all tables with an org_id column:
org_members, subscriptions, usage_events, api_keys, audit_log,
hmrc_credentials, hmrc_tokens, hmrc_businesses, hmrc_annual_adjustments,
transactions, quarterly_summaries,
properties,
bank_connections, bank_accounts, bank_transactions,
agentos_connections, agentos_properties, agentos_tenancies, agentos_transactions,
landlord_links,
notifications, onboarding_steps, feedback
RLS policies are generated and applied to every table in this list, enforcing org_id isolation at the database layer.
2. setOrgContext Applied to All Transaction Code Paths
The setOrgContext function (which sets the PostgreSQL session variable used by RLS policies) is now called at the start of every code path that opens a database transaction. This ensures the organisation context is always established before any query runs, including in background jobs, webhook handlers, and batch operations.
Impact on Existing Behaviour
For correctly written application code that already filtered by org_id, there is no functional change — the RLS policy will match the same rows the application was already selecting.
If any code path was inadvertently running queries without setting the org context, those queries will now return zero rows (rather than all rows) for RLS-protected tables. If you observe unexpected empty result sets after upgrading, check that setOrgContext is being called before your query.
Recommendations for Developers
- Always call
setOrgContext(orgId)at the start of any request handler or background task before issuing queries against tenant-scoped tables. - Do not bypass RLS by connecting with a superuser role in application code. Reserve superuser connections for migrations only.
- Audit any raw SQL in the codebase to confirm it does not rely on the absence of RLS to return cross-tenant data.
- Test with a non-superuser role in development to catch missing context calls early.
Security Classification
| Property | Value |
|---|---|
| Severity | Critical |
| Type | Tenant Isolation / Data Leakage |
| Attack Vector | SQL injection or ORM misconfiguration |
| Data at Risk | HMRC tokens, financial transactions, property data |
| Fix | Database-layer RLS on all org_id tables + setOrgContext coverage |
| Affected File | src/db/rls.ts |