SyncTrix logoSyncTrix
All articles
SaaS Engineering11 min read

Multi-tenant isolation: the leak paths that testing usually misses

Cross-tenant data leaks rarely happen through the endpoints you tested. They happen in background jobs, exports, search indexes and caches.

By Marcus Hale
Multi-tenant isolation: the leak paths that testing usually misses

One customer seeing another customer's data is the failure that ends B2B relationships and triggers breach notifications. The API endpoints usually get this right because that is where the tests are. The leaks come from the paths nobody thinks of as user-facing, and they are consistent enough across products to be worth listing.

01Where isolation actually breaks

Background jobs are the most common culprit. A nightly report generator, a data export, a cache warmer or a search reindexer that queries without a tenant filter will happily process everything, and no request-scoped middleware is there to stop it because there is no request.

Search indexes are second. Documents from every tenant land in one index and the filter is applied at query time, which works until a query path forgets it - autocomplete, a suggestion endpoint, a related-items feature. Caches are third, where a key without the tenant id serves one tenant's data to another.

PathWhy it leaksFix
Background jobsNo request context, filter omittedTenant id required in the job payload
Search indexShared index, filter at query timeFilter enforced in a shared wrapper
Cache keysKey lacks tenant idTenant id in every key, enforced by helper
Exports and reportsBulk query bypasses normal pathSame access layer as the API
WebhooksPayload built from an unscoped queryScope at construction
Admin toolingDeliberately unscoped, then reusedSeparate code path entirely
Leak paths, ranked by how often we find them

02Make the filter impossible to forget

Relying on every developer remembering a WHERE clause is a plan that fails on the first busy Friday. The mechanism has to be structural: row-level security in the database, or a data access layer that requires a tenant context and refuses to build a query without one.

Row-level security is the strongest option because it holds even when application code is wrong, including in ad hoc queries and background jobs. The cost is that it must be configured carefully and that a connection pool sharing sessions across tenants needs explicit handling.

03Choosing the isolation model

Shared schema with a tenant column is the default and scales well operationally. Schema per tenant gives stronger isolation and becomes painful past a few hundred tenants because migrations must run everywhere. Database per tenant is the strongest and the most expensive to operate.

Most products should start shared and move specific large or regulated tenants to dedicated databases when there is a concrete reason. Starting with database-per-tenant because it feels safer usually means operational overhead that the customer base does not yet justify.

ModelIsolationOperational costFits
Shared schema, tenant columnApplication-enforcedLowestMost SaaS
Shared schema plus RLSDatabase-enforcedLowMost SaaS, better default
Schema per tenantStrongModerate, migrations multiplyHundreds of tenants max
Database per tenantStrongestHighRegulated or very large tenants
Isolation models compared

04Test it as an adversary, not as a user

Create two tenants with realistic data and then try to cross the boundary deliberately: manipulate identifiers in every endpoint, run every background job, trigger every export, query search directly, and inspect cache contents.

Automate the identifier manipulation as a test that runs in CI - for every resource endpoint, a request from tenant A for a tenant B object must return not-found rather than forbidden. Returning forbidden confirms the object exists, which is itself a small information leak.

05Detect it, because prevention is not certainty

Log the tenant context on every data access and alert when a query returns rows spanning multiple tenants outside of explicitly allowed admin paths. That detection catches the case your tests did not cover, which is by definition the case you did not think of.

It also matters for the aftermath. If a leak does occur, being able to establish exactly what was accessed and by whom is the difference between a bounded, honest disclosure and a worst-case assumption that you have to make because the logs cannot tell you.

Topics

multi tenant data isolationsaas tenant securityrow level security postgrescross tenant data leakmulti tenancy architecture

Marcus Hale

Lead Architect · SyncTrix

Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.

Building something like this?

SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.

Talk to an engineer