A cross-tenant leak, where one customer sees another customer’s data, is the rare software failure that can end a SaaS company outright. It is almost never an attack. It is usually one query missing one condition.

The decision that prevents it gets made in the first week, by whoever designs the data model, and is expensive to revisit afterwards.

What tenant isolation actually is

Almost every SaaS product is multi-tenant. One application, one database, and a column somewhere recording which customer owns each row. This is not a compromise or a shortcut. It is the arrangement that makes the economics of SaaS work at all, and the alternative that founders reach for under pressure, giving every customer their own everything, mostly transfers the problem into operations.

Tenant isolation is the set of controls guaranteeing that the ownership column is always honoured. Every request that arrives carries an identity, that identity belongs to exactly one customer, and no path through the system returns data belonging to a different one. When it fails it is a case of broken access control, which sits first on the OWASP Top 10.

Stated that way it sounds like a solved problem. It is solved in the sense that the technique is well understood, and unsolved in the sense that it keeps happening to companies with far more engineers than you have.

Why a cross-tenant leak ends companies

Most security incidents are survivable because they are ambiguous. Data was possibly accessed. An attacker was present for some period. You disclose carefully, you improve, most customers stay.

A cross-tenant leak has none of that ambiguity, and that is what makes it different in kind rather than in degree.

A typical breachA cross-tenant leak
Discovered by youDiscovered by your customer
Attributed to an attackerAttributed to your engineering
Scope uncertainScope is “which customers, exactly”
Explained in a blog postExplained in a contract review
Customers mostly stayProcurement reopens every deal

The commercial consequence follows from the second row. Buyers forgive being attacked, because they are being attacked too. They do not forgive a product that handed their data to a competitor through ordinary operation, because the failure says something about your engineering that a determined adversary does not.

There is a regulatory consequence as well. Disclosure of personal data to an unauthorised third party is a reportable event under most privacy regimes, and the affected parties are identifiable by definition, which removes the argument that impact could not be established.

So this is not a technical preference. It is the one architectural decision on your roadmap with the power to end the business, and it is made in the first week by whoever is designing the data model, usually without anyone framing it that way.

Shared schema, schema per tenant, or database per tenant

Every product picks one of these, explicitly or by default.

ModelHow it separatesWhat it costs
Shared schemaAll tenants in the same tables, separated by a tenant columnCheapest to run. Isolation depends entirely on enforcement
Schema per tenantEach tenant gets its own set of tables in one databaseModerate. Separation is structural, migrations multiply
Database per tenantEach tenant gets a database of their ownStrongest separation. Every operational task multiplies by the number of customers

Founders tend to treat this as a spectrum from insecure to secure, and choose based on how nervous they feel. That reading is wrong in a way that matters.

Moving down that table does not remove the possibility of a leak. It changes which mistake causes one. In a shared schema, the mistake is a missing condition in a query. In a database per tenant, the mistake is a connection pool handing back the wrong connection, or a migration script run against the wrong target, or a backup restored into the wrong customer’s environment. The second category is rarer, but it is also harder to test for and tends to fail more comprehensively when it does.

The honest framing is a cost decision with a security component. Choose the shared model unless a specific buyer is paying you for something stronger, and then spend the money you saved on making enforcement structural rather than conventional.

Where isolation gets enforced: application or database

This is the decision that actually determines your exposure, and it is independent of which model you picked.

  • Enforced in application code. Every query must remember to filter by tenant. This is correct only if all of them do, forever, including the one written at speed on a Friday and the one added by a new hire. A forgotten filter returns another customer’s data.
  • Enforced in the database. The database attaches the rule to the table itself. A forgotten filter returns nothing. The failure mode of a mistake is an empty page, not an incident.

Everything else in this article is detail. This is the article.

Application-level filtering is not weaker because developers are careless. It is weaker because it requires a property to hold across every current and future query in the codebase, and no review process reliably maintains that property as a team grows and moves quickly. It is a control whose effectiveness decays with headcount and velocity, which is to say it decays exactly as you succeed.

Database-level enforcement inverts the default. Access is denied unless a policy permits it, so the cost of forgetting is a bug report about missing data rather than a disclosure notice. The relevant feature in most relational databases, PostgreSQL’s implementation being the most commonly cited, is row-level security, and the reason to care about it as a founder is not the mechanism but the direction of the failure.

The catch is that this is genuinely cheap now and genuinely expensive later. Retrofitting enforcement into a mature product means auditing every table, every existing query, and every background job that has been quietly relying on unrestricted access. It is the clearest example on the whole roadmap of a decision that costs an afternoon in month one and a quarter in year three.

Six ways tenant isolation fails

The failures are consistent, and only the first is the one people expect.

  • The forgotten filter. One endpoint, usually a new one, usually a listing or an export or a search, that omits the tenant condition. This is the textbook case and the easiest to prevent.
  • The identifier taken from the request. The application filters by tenant correctly, but reads the tenant from a value the client supplied rather than from the authenticated session. Changing a number in a URL becomes a valid way to read someone else’s data. This passes every review that only checks whether filtering exists.
  • The policy that was never enabled. Database policies are written, reviewed, and merged, but never activated on the table. The code looks right and enforces nothing.
  • The admin connection that became the app connection. A credential that bypasses policies by design, appropriate for migrations, quietly reused as the application’s own connection because it made an error go away.
  • The path that is not the application. Background jobs, scheduled reports, data exports, the internal support tool, and the analytics pipeline. These are written by the same team under less scrutiny, and the support tool in particular is designed to see everything, which makes it the single most dangerous piece of software you own.
  • Caching by the wrong key. The query is correct, the response is stored against a key that omits the tenant, and the next customer is served the previous customer’s data by a layer nobody classified as security relevant.

The pattern across all six is that isolation fails at the seams, not in the middle. The main product flow is the part everyone looks at.

Questions to ask about tenant isolation

Put these to your team, and expect them from any enterprise buyer who has been burned before.

  • If a developer wrote a new listing endpoint today and forgot the tenant condition entirely, what happens? If the answer is anything other than “it returns nothing”, enforcement is conventional rather than structural.
  • Where does the tenant identity come from on each request, and is there any path where it originates from something the client sent?
  • Which components connect to the database with a credential that bypasses isolation, and is any of them serving user traffic?
  • Do we have a test that authenticates as one tenant, requests another tenant’s record, and fails the build if it succeeds? Does it run on every deploy?
  • Which systems other than the main application read the customer data store, and who reviewed those?
  • If we had to tell a customer exactly which of their records had been exposed, could we, and from what?

Start here. Write one test that authenticates as one tenant, requests another tenant’s record, and fails the build if it succeeds. It is worth more than any amount of architecture confidence, because it is the only thing on this list that keeps being true after everyone who read this article has left.

Every other security control protects your customers from an attacker. Tenant isolation protects your customers from you.