All Docs
FeaturesCalmony PayUpdated March 15, 2026

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:

SettingValue
ENABLE ROW LEVEL SECURITYActivates RLS on the table
FORCE ROW LEVEL SECURITYApplies RLS to table owners too
tenant_isolation policyorg_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_id does not match the org_id of the requested row.

Files Changed

  • src/db/rls.ts — Added all Calmony Pay tables to RLS enforcement