All Docs
FeaturesBlockManOSUpdated March 11, 2026

Security Notice: Incomplete Row-Level Security Coverage Across Domain Tables

Security Notice: Incomplete Row-Level Security Coverage Across Domain Tables

Version: 1.0.12
Severity: 🔴 Critical
Category: Enterprise Infrastructure / Multi-Tenant Security


Overview

A critical gap has been identified in the platform's Row-Level Security (RLS) implementation. Database-level RLS policies are currently enforced on only 6 of the 30+ tables in the system. The remaining domain tables — which hold the core Irish block management data — depend exclusively on application-level tenant filtering. This does not provide sufficient isolation in a multi-tenant SaaS environment.


Background: RLS in a Multi-Tenant Platform

The platform serves multiple Owners' Management Companies (OMCs) and property management agents, each operating as a separate organisation (orgId). Strict data isolation between tenants is a fundamental security and compliance requirement.

Row-Level Security (RLS) enforces this isolation at the PostgreSQL database level, meaning that even if application code has a bug or is bypassed, the database itself will refuse to return rows belonging to another tenant.


What Was Found

The rls.ts file defines RLS policies for the following 6 tables only:

TableRLS Enforced
org_members✅ Yes
subscriptions✅ Yes
usage_events✅ Yes
api_keys✅ Yes
audit_log✅ Yes
webhooks✅ Yes

The following domain tables (and more) have no database-level RLS policies:

TableRLS Enforced
developments❌ No
units❌ No
owners❌ No
serviceChargeDemands❌ No
complianceObligations❌ No
maintenanceRequests❌ No
(30+ additional domain tables)❌ No

How the Gap Arises

For unprotected tables, tenant isolation is handled entirely within the application layer using a filter of the form:

and(eq(table.orgId, ctx.orgId))

This pattern is applied consistently in query builders throughout the codebase. However, it has two critical weaknesses:

  1. SQL Injection: A successful injection attack can craft queries that bypass or remove the orgId filter, exposing rows from other tenants.
  2. Application Bugs: A logic error, missing filter, or code path that skips the orgId check can inadvertently return or mutate another tenant's data.

Database-level RLS eliminates both risks because the filter is enforced unconditionally by PostgreSQL, regardless of how the query was constructed.


Risk Assessment

FactorDetail
ConfidentialityCross-tenant leakage of owner PII, financial records, and compliance data is possible
IntegrityWrites or mutations could potentially affect another tenant's records
RegulatoryIrish data protection obligations (GDPR) apply to owner and unit holder data
Blast radiusAll 30+ domain tables and every OMC organisation on the platform

Required Remediation

RLS policies must be extended to cover all domain tables. The existing policies in rls.ts for the 6 covered tables provide a reference pattern to follow. Key steps:

  1. Audit all tables — enumerate every table in the schema that stores tenant-scoped data.
  2. Define RLS policies in rls.ts — add SELECT, INSERT, UPDATE, and DELETE policies for each table, scoped to current_setting('app.org_id') or the equivalent session variable.
  3. Enable RLS on each table — ensure ALTER TABLE ... ENABLE ROW LEVEL SECURITY is applied.
  4. Retain application-level filteringorgId filters in query builders should remain as a defence-in-depth layer.
  5. Test cross-tenant isolation — add integration tests that verify a session for Org A cannot read or write rows belonging to Org B for every domain table.

Interim Mitigations

While full RLS coverage is being implemented, the following measures reduce (but do not eliminate) risk:

  • Conduct a thorough audit of all query builders to confirm orgId filters are applied consistently.
  • Review and harden input validation and parameterisation to reduce SQL injection surface area.
  • Enable database query logging and alerting for queries that return an unexpectedly high number of rows across apparent tenant boundaries.
  • Restrict direct database access to application service accounts only; revoke any broader credentials.

References