A lot of what I write about focuses on one user seeing another user's data. If you're building something multiple businesses use, a piece of software each company signs up for separately, there's a whole extra layer sitting above that: making sure one company's entire account, every user inside it, never crosses into another company's data at all.
Why this is a different problem than user-level isolation
Most access-control advice focuses on the user level: can user A see user B's record. Multi-tenant apps need that same check, plus a second, higher-level one: is every single query in this app scoped to the current company, not just the current user. Get the user-level check right but miss the tenant-level one, and a bug can leak an entire company's records to a completely unrelated business, which is a much bigger incident than one user's data reaching another user in the same organization.
Where this actually breaks in practice
- A query filters by user ID correctly, but forgets to also filter by company ID, so a user in company A who somehow gets a valid record ID from company B can pull it up, because the query never checked which company it belonged to in the first place.
- An admin feature meant for one company's internal admin accidentally has no check confirming that admin's own company ID matches the data they're viewing, letting a company's admin account reach another company's records.
- A shared background job, a report generator, a nightly export, an AI feature summarizing activity, that operates across all data because it was built before tenant isolation was thought through carefully, and never got scoped down afterward.
- An API endpoint accessed with an API key that identifies the calling company but where the endpoint itself doesn't actually check that key against the tenant ID on the requested resource.
The specific test worth running
Create two test accounts under two entirely separate company signups, not two users in the same company. Log in as company A and deliberately try to reach company B's data: change an ID in a URL, call an API directly with company A's credentials but company B's record ID, check any report or export feature for accidental cross-company leakage. This test catches an entirely different class of bug than the standard two-test-accounts check, because it's checking the tenant boundary specifically, not just the user boundary.
Why AI-scaffolded multi-tenant apps are especially worth double-checking
Ask an AI coding tool to build multi-tenant features and it will often get the user-level filtering right, because that pattern is extremely common in its training. Tenant-level filtering, especially across every single query and background process in a growing app, is a more architectural decision that's easy to apply consistently at the start and easy to forget on the fifteenth feature added three months later. Every new table and every new background process is a fresh chance for tenant scoping to quietly not make the trip.
If you're building something multiple separate companies will use, this is worth its own dedicated check, run regularly as new features ship, not folded into the general 'does one user see another's data' test and assumed covered.