Security: RLS Extended to All Calmony Pay Tables (SEC-04)
Security: RLS Extended to All Calmony Pay Tables (SEC-04)
Release: v1.0.93
Control: SEC-04
Category: OWASP — Access Control Enforcement
Background
Calmony Pay enforces organisation scoping at the application layer through ctx.orgId and ctx.projectId checks in every route handler. This RBAC layer is the primary access control mechanism.
As a defence-in-depth measure, the platform also applies PostgreSQL Row-Level Security (RLS) directly at the database layer, ensuring that even in the event of an application-layer failure (e.g. SQL injection, ORM misconfiguration), cross-tenant data access is blocked by the database itself.
Prior to v1.0.93, RLS was configured only for the original platform tables. The entire Calmony Pay payment data tier — 11 tables in total — was missing from the RLS table list in src/db/rls.ts.
What Changed
A PAY_RLS_TABLES array has been added to src/db/rls.ts covering all Calmony Pay tables that contain an org_id column. For each table, the following are now applied:
| Setting | Value |
|---|---|
ENABLE ROW LEVEL SECURITY | Activates RLS on the table |
FORCE ROW LEVEL SECURITY | Applies RLS to table owners too |
tenant_isolation policy | org_id = current_setting('app.current_org_id', true)::text |
Tables Now Under RLS
pay_projects
pay_customers
pay_payment_methods
pay_payment_intents
pay_subscriptions
pay_invoices
pay_checkout_sessions
pay_webhook_endpoints
pay_api_keys
pay_events
pay_webhook_deliveries
How the Tenant Isolation Policy Works
The tenant_isolation policy uses a PostgreSQL session-level setting to identify the current organisation:
CREATE POLICY tenant_isolation ON pay_customers
USING (org_id = current_setting('app.current_org_id', true)::text);
Before executing any query, the application sets the session variable:
SELECT set_config('app.current_org_id', '<org_id>', true);
With FORCE ROW LEVEL SECURITY enabled, this policy is enforced for all database roles — including superusers acting on behalf of application connections — providing a hard database-layer boundary around each organisation's payment data.
Security Impact
This change closes the defence-in-depth gap identified in OWASP control SEC-04:
- Before: Application RBAC was the sole access control layer for payment data. A bypassed or misconfigured route handler could potentially expose cross-tenant payment records.
- After: Even if the application layer is compromised, the database will reject any query where
app.current_org_iddoes not match theorg_idof the requested row.
Files Changed
src/db/rls.ts— Added all Calmony Pay tables to RLS enforcement