YG Yusuf Ghyasi FOUNDER · ENGINEER
Profile 01 Agaro 03 Work 04 Doctrine 05 Research Contact
← ARCHIVE RA-020
ERP SYSTEMS

Multi-Tenancy and the Discipline of Isolation

In a platform that serves many businesses, the most important code is the code that keeps their data apart. There is no acceptable failure rate.

YUSUF GHYASI March 27, 2026 8 MIN READ

There is exactly one bug in a multi-tenant ERP that can end the company building it: one customer seeing another customer’s data. Everything else is recoverable. A wrong invoice total gets corrected. A slow page gets optimized. A cross-tenant leak gets you a breach disclosure, a churned customer list, and a reputation you do not get back. So when people ask what the most important code in my platform is, the answer is boring and absolute: it is the code that keeps tenants apart, and it has no acceptable failure rate.

That phrase matters. Most engineering targets are probabilistic. Five-nines uptime accepts a few minutes of downtime a year. A good test suite accepts that some bugs slip. Isolation does not get a budget. One leak is one too many, because the cost is not proportional to the size of the leak. A single leaked record is a breach.

Why this is hard to get right

The naive mental model is that you check the tenant on the way in and you are done. Add a tenantId to every query, filter on it, move on. That works until it doesn’t, and the ways it stops working are exactly the ways that don’t show up in a demo.

The failure is almost never the query you remembered to filter. It is the one you forgot. A new endpoint a junior dev added on a Friday. A background job that operates across records and was written before isolation was a discipline. A bulk operation that takes a list of ids and trusts them. A report that joins three tables and only filters one of them. An admin tool that was “internal only” until it wasn’t. The leak hides in the gap between the rule and its enforcement, and the gap grows every time someone ships without thinking about tenancy.

If isolation depends on every developer remembering to filter every query, you do not have isolation. You have a streak that has not broken yet.

The discipline, then, is to make isolation structural rather than remembered. The goal is a system where it is hard to write a leaking query, not one where you hope nobody does.

Defense in layers

I do not trust any single layer to hold. I stack them so that a miss in one is caught by the next.

Layer one: identity carries tenancy. Every authenticated request resolves to a principal that is bound to exactly one tenant. The tenant is never taken from user-supplied input — not from a header, not from a query param, not from the request body. It comes from the trusted session. The most common IDOR I have found in audits is a system that reads the tenant from something the attacker controls. If the client can name the tenant, the client can name someone else’s.

Layer two: the data access boundary enforces it. Tenant scoping does not live in individual queries scattered across the codebase. It lives in the access layer that all queries pass through, applied by default. A developer writing a new query gets the tenant filter whether or not they think about it. The safe path is the default path. Opting out is loud and reviewable.

Layer three: the action surface checks ownership. Every typed operation — record a payment, delete an employee, assign a service — re-verifies that the target record belongs to the caller’s tenant before it acts. This is the layer that catches the bulk-operation and stale-id cases. You hand it a list of ids, it confirms every one is yours, and it rejects the whole call if any isn’t. This is the same tool surface my AI uses, which means the AI inherits the exact same ownership checks a human gets. An agent can no more reach across the tenant boundary than a user can.

Layer four: tests that try to break out. Negative tests are the ones that matter here. Not “can tenant A read tenant A’s data” — of course it can. The tests that earn their keep are “tenant A, authenticated and well-formed, attempts to read, write, list, export, and delete tenant B’s data through every endpoint, and every attempt is denied.” Those tests are tedious to write and they are the only ones that prove the property.

# The test that actually matters
as(tenantA):
  for each endpoint that takes an id or a list:
    target tenantB's records
    assert -> 403 or empty, never tenantB's data
    assert -> no partial leak in error messages or counts

That last assertion catches a subtle one: a “not found” that differs from a “forbidden” can leak existence. Even confirming that a record exists in another tenant is a leak. The denial has to be uniform.

The shared-vs-separate question

People expect this discussion to be about whether you give each tenant a separate database or share one with a discriminator column. It is a real decision with real tradeoffs.

Shared schema, tenant columnDatabase-per-tenant
Isolation strengthLogical, enforced in codePhysical, enforced by infra
Cost at scaleLowHigh
Cross-tenant analyticsEasyHard
Blast radius of a code bugAll tenantsOne tenant
Operational complexityLowerHigher, multiplies with tenants

I run a shared model for most workloads because the economics of serving the mid-market demand it, and because database-per-tenant does not actually remove the discipline — it just moves it to provisioning and migrations, which have their own ways to leak. The physical separation is comforting but it is not a substitute for getting the code right. A shared model done with real discipline is safer than a separated model done casually, because the separated model tempts you into believing the infrastructure has your back when your application logic still routes the connection.

The deciding factor for me is blast radius for the most sensitive tenants. There are customers — regulated, federal-adjacent — where physical isolation is worth the cost regardless of the engineering elegance of the shared model. That is a judgment call, not a dogma.

Living with no acceptable failure rate

The practical consequence of treating isolation as non-negotiable is that it changes how you ship. Tenancy is not a feature you add. It is a property every feature must preserve, which means it belongs in code review every single time, in the test suite every single time, and in the threat model of every new surface.

I have come to think of it the way a structural engineer thinks about a load-bearing wall. You do not celebrate the wall. Nobody compliments you on the wall. But you also never, ever cut a doorway through it without proving the load still carries, because the failure mode is not an inconvenience. It is collapse.

A multi-tenant platform earns the right to serve many businesses by proving, continuously and structurally, that it keeps them apart. That proof is unglamorous, it shows up in no demo, and it is the most important code I write. Built for missions includes the mission of never, not once, letting one customer become another customer’s breach.

MULTI-TENANCYISOLATIONSECURITYERPARCHITECTURE