Can one customer access another customer's data in your SaaS?
Check tenant isolation across database queries, files, caches, and background jobs before adding more ways to read customer data.

in this article
Two companies use the same application. Each sees its name in the header, its own orders, and its own reports. That looks like isolation, but the interface only displays what it receives. Protection needs to happen before the server returns the data.
Consider a route that finds an order by its identifier. If the server only checks for a valid session, an authenticated person might receive another company's order. The interface does not even need to show a button that opens it.
This is a useful test before adding more reports or AI search. Every new way to read data needs to respect the separation between customers.
Resolve the organization on the server
The browser can tell the server which organization someone selected. The server needs to verify the session, confirm membership in that organization, and authorize the requested action. An identifier submitted in a form does not prove access.
OWASP's authorization guidance recommends denying access by default and checking permissions on every request. Hiding interface controls does not replace that check.
After authorizing the read, a parameterized query can restrict the order to the verified organization:
SELECT id, status, created_at
FROM orders
WHERE organization_id = $1
AND id = $2;
Here, $1 comes from the server-validated context. $2 is the requested order. This query illustrates organization filtering, but does not implement the full access policy. A company member might only have permission to see their own orders.
Apply the same reasoning to updates and deletes. Check relationships, too. An order in company A should not accept a reference to a customer in company B merely because both records exist.
The database can reinforce the rule
PostgreSQL supports row security policies. Superusers and roles with BYPASSRLS bypass them. Table owners normally bypass them too, unless FORCE ROW LEVEL SECURITY applies.
If you adopt this feature, test with the role the application uses in production. A test run as an administrator can misrepresent the protection. Check which organization context reaches the database and how it ends after each operation.
For reused connections, we recommend passing that context within a transaction and limiting its lifetime to the transaction. The next job must not inherit the previous organization. Missing context should prevent access, and that case deserves its own test.
Find the exits beyond the list page
An application can protect its order query and leak data through an exported report. Inventory the paths that deliver information to users.
A cache key needs to distinguish the organization and relevant access scope. A cache hit must still respect current authorization. For file storage, check access before issuing a temporary download link.
For a background job, record the organization and the responsible identity. Define whether it uses the requester's current permission or an explicit service permission. Removing someone from a company must not leave that decision to chance.
Include AI search in this review. Filter authorized documents before sending them to the model. A prompt telling the model not to reveal other customers' data cannot repair a query that already retrieved it.
Always test with two companies
Create fictional data for two organizations. Run the same operation with a user from each and try crossing the identifiers. Cover reads, writes, and downloads.
Repeat a query after the other organization has populated the cache. Test a job created before a user's removal. Check a request with no organization context as well. These scenarios exercise paths that a login test never reaches.
The expected outcome includes the effect on storage. Refusing the response after saving an unauthorized change is not sufficient protection.
Before releasing the next feature, list the data it reads and the routes through which that data leaves. Follow that list as a user from the other company. That test tells you more than the correct company name in the header.