apointoo.
HIPAA

PostgreSQL RLS vs Application Tenant Filters

cmsapointoo··7 min read

Short answer: PostgreSQL row-level security is a database-enforced backstop, while an application tenant filter is a code convention that every caller must apply correctly. As of 2026-08-15, Google Cloud documents Cloud SQL editions, AWS documents PostgreSQL pricing, and 45 CFR 164.308 requires administrative safeguards and risk management. Neither control is complete without membership checks, privileged-role review, restore testing, and evidence.

Isolation model and threat boundary

The primary difference is where a missed filter is caught. An application filter adds a predicate in shared code and depends on every caller, query, job, report, and export preserving it. PostgreSQL RLS evaluates database policies for applicable roles and operations, creating a backstop when the application query is incomplete. That does not make the database universally safe, because privileged roles and alternate stores can bypass it.

Define the tenant boundary before choosing a control. A request has an authenticated principal, server-resolved membership, immutable home region, operation, and data scope. The application checks those values. The database policy then checks a transaction-scoped tenant context or equivalent server-side context. If context is missing, the desired result is denial or an empty scope, not a broad query.

45 CFR 164.308 describes administrative safeguards and risk management, not a required database product. The technical choice should be documented in the environment-specific risk analysis. Cloud SQL Editions and RDS PostgreSQL Pricing can inform platform and cost decisions, but a provider page does not establish isolation or legal sufficiency.

Control Strength Failure to test
Application filter Simple and portable One omitted predicate can widen a result
RLS backstop Database policy covers tested roles Privileged role or missing context can bypass
Membership Maps caller to tenant and role Client input or stale session can be trusted
Operations Controls exports, backups, and support Non-web paths may avoid both filters

Read Cloud SQL PostgreSQL RLS, compare RDS PostgreSQL and DynamoDB, and review Firestore Rules versus Admin SDK.

Enforcement path and bypasses

Application filters and RLS share the same identity prerequisite. Authenticate the caller, resolve membership and home region server-side, authorize the operation, and then set database context. Never let a browser choose a tenant predicate or a database context. A wrong-tenant request should fail before a useful row is returned.

For application filtering, grep and review every caller: web routes, scheduled jobs, queue consumers, reports, exports, admin views, support tools, migrations, and tests. A shared helper reduces duplication but does not prevent a direct query or new caller from bypassing it. Negative tests must assert that a wrong tenant cannot be read or changed.

For RLS, test session context, connection pooling, transaction boundaries, table policies, indexes, views, functions, owners, migrations, reporting roles, and direct database connections. A privileged role may bypass policy by design. Treat that role as a separate access path with approval, logging, and limited use.

Keep data minimum necessary in either model. A generic conversion record may use approved event type, time, currency, value, and an opaque reference. Do not send patient names, email addresses, phone numbers, hashed identifiers, service names, treatment details, or clinical text to advertising systems. Tenant isolation does not authorize an external disclosure.

caller
  -> membership and home region
  -> application authorization
  -> transaction tenant context
  -> RLS policy and query filter
  -> audit event without full row

Audit, restore, support, and offboarding

Off-path operations decide whether isolation is real. Run two synthetic tenants through web, queue, report, export, support, backup, restore, migration, and administrative paths. For each operation record identity, tenant context, expected result, observed result, and layer that enforced the decision.

Backups contain data outside the live query path. Restore into an isolated target, install roles and policies, verify tenant denial, inspect keys and logs, and delete the test copy. An application filter may not run during restore, and RLS may be absent until schema migration completes. The restore gate must explicitly test both.

Support access should be separate from application access. Use a time-limited approval, narrow tenant scope, reason code, and audit event. Test an operator who has no membership, an operator with one tenant, and an emergency operator. Do not create a permanent broad role to avoid an incident delay.

Offboarding needs return or destruction evidence. Identify live rows, queues, exports, backups, logs, and test copies. A database policy does not delete them. A code filter does not delete them. Retention, legal hold, and customer contract determine what remains and for how long.

Runnable proof tests

A small negative-test matrix is more valuable than a long positive demo. Use synthetic records for tenant A and tenant B.

  1. Read and update own-tenant records through the web path.
  2. Change tenant input and confirm denial before data access.
  3. Remove membership and confirm denial.
  4. Run reports, queue jobs, exports, direct database queries, and admin tools.
  5. For RLS, remove context, reuse a pooled connection, and use a privileged role.
  6. Back up and restore, then repeat tenant tests.
  7. Inspect logs, keys, support access, offboarding, and deletion evidence.

Keep a failing test for every known bypass. If one route remains code-only, say so in the risk record. If RLS covers only ordinary roles, state the privileged exception. The result is a bounded control claim instead of an unsupported promise.

Make the context contract explicit. Name the authenticated principal, membership lookup, tenant identifier, home region, transaction scope, database role, policy, and expected denial. Reject a request when any value is missing or inconsistent. Do not infer tenant from a record path, referrer, hostname, or browser state. Those values can help route a request, but they do not authorize a row.

Connection pooling is a high-value test because it joins separate requests to one database process. Run two concurrent synthetic tenants, change context, force an error, retry, and inspect both results. Repeat with a report and queue worker. If a context survives longer than intended, reset it or use a transaction-scoped design. If a missing context returns broad data, stop the deployment.

Review database owners, functions, views, migrations, foreign keys, indexes, exports, and administrative clients. RLS can be bypassed by a privileged role; code filters can be bypassed by a new caller. Record those exceptions and give each an owner, approval path, and negative test. Do not describe a control as universal when a known path is outside it.

Offboarding should be tested against live rows, outbox events, queues, reports, exports, backups, logs, and recovery copies. A tenant filter or row policy controls access, not deletion. Contract, legal hold, risk, and retention determine what remains. The Cloud SQL RLS guide gives a provider-specific context pattern, while logging retention covers evidence without copying full rows.

Choose the stronger control only after a measured proof. A database policy can reduce the blast radius of a missed filter, but it adds context, role, migration, and operational work. A code-only filter can be acceptable for a bounded non-sensitive path, but a critical shared store should document why no database backstop exists and how every caller is kept in scope.

Frequently asked questions

Is RLS always better than application filters?

RLS provides a useful database backstop, but it still needs safe context, role review, and testing. Application filters remain necessary for membership and may be the only control on non-database paths.

Can RLS protect backups and exports?

Not automatically. Backup, restore, export, support, queue, and admin workflows need their own identity, scope, retention, and negative tests.

What is the worst code-only failure?

A caller omits the tenant predicate or uses an untrusted tenant value and receives another tenant’s result. Search every caller and keep a negative test that would fail when the filter disappears.

What must be approved before production?

Engineering must prove identity, policy, bypass, restore, support, and offboarding paths. The customer and counsel must approve contracts, data use, retention, transfers, and legal questions.

References

Related articles