← All posts

Multi-tenant isolation: a WHERE clause is not a security boundary

30 June 2026 · Nasugn NDMS · 2 min read
multi-tenancyrow-level securitypostgressecurityisolation

Every multi-tenant application makes the same promise: your data is yours, and no other customer can see it. The question worth asking any vendor is how that promise is kept — because there’s a big difference between “we filter by tenant in our code” and “the database refuses to return another tenant’s rows.”

The fragile default: WHERE tenant_id = ?

The common approach is to add a tenant filter to every query. It works, right up until it doesn’t. All it takes is one endpoint where a developer forgets the clause, one raw query built for a report, one JOIN that widens the scope, one clever ORM call that skips the usual path. There’s no single place that guarantees isolation — it’s re-asserted by hand in hundreds of queries, and it only takes one miss to leak.

These bugs are quiet. Tests pass, because tests usually run as one tenant. The leak shows up when a real customer sees a document that isn’t theirs — the worst possible moment, and the worst possible way, to discover the boundary was never really there.

The durable approach: row-level security

NDMS puts isolation in the database with Postgres row-level security (RLS). Every tenant-owned table carries a policy: a row is only visible when its tenantId matches the tenant of the current session. The application sets that tenant context at the start of each request, and from then on Postgres itself filters every read and write — SELECT, JOIN, report query, all of it.

Two properties make this strong:

  • It’s default-deny. With no tenant context set, the tables return nothing. A forgotten filter fails closed (empty), not open (everyone’s data).
  • The app runs as a non-privileged role. NDMS connects to the database as a role that is subject to RLS — not the owner that could bypass it. So even a bug in application code can’t read across tenants, because the code never had that power to begin with.

Defence in depth, not a single wall

RLS doesn’t replace application-level checks — it backs them up. Requests still resolve the tenant from the authenticated session (never from client input), and roles still gate what each user can do. RLS is the layer underneath that holds even when something above it slips. On top, per-tenant KMS keys separate data cryptographically on shared storage.

The point isn’t that any one of these is unbeatable. It’s that isolation shouldn’t rest on every developer remembering a filter forever. It should be a property of the system.

What to ask your vendor

Next time a multi-tenant vendor says “your data is isolated,” ask the follow-up: isolated by what? If the answer is “our code filters by account,” you’re trusting their discipline on every query they’ll ever write. If the answer is “the database enforces it and our app can’t bypass it,” the boundary is real — and it stays real as the codebase grows.


Ready to see it? NDMS runs the whole document lifecycle — capture, search, e-signature, retention and disposal — with hard tenant isolation and a full audit trail. Start free →