Multi-tenant data isolation belongs in the database, not the code.
When one system serves many companies at once, multi-tenant data isolation is the rule that keeps each one apart. One running system, one database, dozens of separate businesses inside it. Each company has its contacts, its records, its history, its deals. None of them can ever see another’s data. Not by accident, not through a clever URL, not when a line of code has a bug.
There are two places you can put that wall. You can put it in the application code, the layer that answers each request. Or you can put it in the database, the layer that holds the rows. Most teams put it in the code. I put it in the database. That one choice decides whether the wall holds under every query or only under the queries someone remembered to guard.
Why the application code is the wrong place for the wall
Code is where bugs live. Every query you write is a chance to forget one line. You mean to say show me this company’s records belonging to the company asking, and one tired afternoon you write the first half and not the second. The query runs. It returns every record from every company in the system. The page looks fine. The leak is silent. Nobody notices until the wrong person does.
This is the failure that most multi-tenant systems carry without knowing it. The protection is one filter, repeated by hand on hundreds of queries, and it only takes one missed filter to open the door. You cannot test your way to confidence here, because the gap is the one query you forgot existed. A wall that depends on perfect memory across every line a team will ever write is not a wall. It is a hope.
How row-level security keeps each company’s data apart
So I moved the wall down a level. Every table that holds customer data carries one column: which company owns this row. The database itself enforces a rule on that column. Before any row leaves the table, the database checks who is asking and which companies they belong to. If the owner of the row is not on that list, the row does not come back.
This runs inside the database, under every query, whether the code remembered to ask for it or not. Read, write, change, delete. Four rules, the same shape, on every single table. They all point at one small function that answers one question: which companies does this person belong to. Write the rule once and it covers every query that table will ever see, including the queries no one has written yet.
The application code still adds its own filter on top. Belt and suspenders. I treat that filter as a convenience, not a defense. Delete every one of those filters tomorrow and no company would see another’s data. The wall would still stand, because the wall is not in the code. The code can fail open. The database fails shut.
Why a code-only audit cannot see the real wall
Here is a truth that catches a lot of people. Point an automated security scan at a system like this and it will read the source, find no protection written in the queries, and raise an alarm. Tables exposed. One company able to read another’s records. If you trust a scan of the code, that reads like a fire.
It is not a fire. The scan looked where the wall used to live and found an empty spot, because the wall moved down a level the scan never checks. The protection is in the database, set to refuse everyone by default, and the source code says nothing about it. A tool that only reads code will misjudge a system that protects its data in the database. Sometimes it panics over walls that are already standing. Sometimes it waves through a gap that is real. The only honest answer comes from asking the system that holds the rows.
So that is what I do. I ask the live database directly. I sign in as one company and confirm I see that company and nothing else. I check that every table set to refuse everyone also carries the rule that lets the right company through, because a table that refuses everyone with no exception does not throw an error. It returns nothing, quietly, and a blank page sends you hunting for a problem that was never there. One pattern on every table. No exceptions, no special cases to remember, nothing that depends on reading the right line in the right file.
Where to put the wall
People ask what keeps one company’s data safe from another’s in a shared system. The answer is not a promise in a sales deck. It is where you decided to put the wall. Put it in the code and you are trusting every query you will ever write, plus every query the next person writes after you. Put it in the database and you are trusting one rule, enforced once, under everything.
That is the difference between a system that holds and a system that hopes. A code-level wall asks you to be perfect forever. A database-level wall asks you to be right once. I know which one I want holding the line at three in the morning. The database does not get tired.