#Abstract
In every cross-tenant disclosure the author has examined, the cause was not a flaw in the isolation design but one query missing one predicate, written by somebody competent, in code that reads as correct. That is an observed pattern across a handful of incidents rather than a measured population. This note argues that tenant isolation belongs in the storage engine rather than the application, gives the reasoning in terms of two long-standing security principles, and proposes a test whose failure indicates the boundary is not where the team believes it is.
#The problem
The common implementation of multi-tenancy is a column. Each row carries a tenant identifier, each query carries a predicate, and a review convention requires checking for it.
This holds for queries in the usual shape and fails for the ones outside it: administrative reports, migration backfills, aggregate counts, and joins that filter one side and not the other. In each case the correct query and the leaking query are visually similar, which is what makes the class persistent rather than a matter of care.
A safety property that depends on an author remembering something is not a property. It is a habit, and habits have a measurable failure rate.
#Two principles that decide the question
Saltzer and Schroeder give complete mediation, meaning every access passes through the check with no path around it, and fail-safe defaults, meaning the default decision is denial . Application-level predicates satisfy neither. A query that omits the predicate has bypassed the check, and its default behaviour is to return everything.
The end-to-end argument identifies which layer can enforce a property completely . For tenancy that layer is the storage engine, because it is the only component that observes every access regardless of which code path produced it.
#Construction
The tenant is established once, at the request boundary, from validated credentials, and is carried on the request context. No component downstream may set it.
Each engine then enforces the boundary using its own mechanism, since the mechanisms differ in strength and this should be stated rather than hidden:
- A relational store applies a transaction-scoped setting plus row-level policies, with the application role holding no bypass privilege. A query with no predicate returns the empty set.
- A graph store is partitioned per tenant, so a cross-tenant traversal has no edge to follow. This is the strongest of the four because it is not a policy at all.
- A search index is provisioned per tenant behind an alias with routing.
- A key-value cache uses prefixes produced by a single key-building function. This is the weakest and relies on discipline rather than enforcement.
#The test
Row-level security has a specific failure that makes an incorrect implementation pass its own tests: the table owner bypasses the policy, so a test run as owner exercises nothing.
The proposed test connects as a role with no bypass privilege, writes as tenant A, switches to tenant B, and issues the broadest query the layer permits with no predicate. The assertion is that the result is empty, not that it is correctly filtered, because B has written nothing. A non-empty result indicates the boundary is above the engine.
1rows, err := nonSuperuserPool.Query(ctxTenantB, "SELECT id FROM assets")2require.NoError(t, err)3require.Empty(t, collect(rows)) // A's rows are invisible, not filtered
#What this does not address
Tenancy is not authorisation. Establishing which customer's data a request may reach says nothing about whether a given subject may perform a given action on a given object, which is the question role-based and attribute-based models address , and which relationship-based systems answer at scale . Conflating the two produces a function whose guarantee nobody can state.
It also does not address noisy neighbours, per-tenant backup granularity, or data residency, and it does not remove the need for identity controls above it .
#Limitations
The argument rests on the claim that leaks are dominated by omitted predicates rather than by flawed policy design. That matches the incidents I have examined and it is not a measured population, and a survey of disclosed multi-tenant incidents classified by root cause would settle it. That survey has not been done here.
The cache mechanism is materially weaker than the other three, and the note does not offer a stronger one. Where cache contents are sensitive, the honest response is per-tenant instances rather than prefixes.
References
- [1]Jerome H. Saltzer, Michael D. Schroeder, “The Protection of Information in Computer Systems”, Proceedings of the IEEE, vol. 63, no. 9, pp. 1278-1308, 1975doi:10.1109/PROC.1975.9939 ↗
- [2]J. H. Saltzer, D. P. Reed, D. D. Clark, “End-to-End Arguments in System Design”, ACM Transactions on Computer Systems, vol. 2, no. 4, pp. 277-288, 1984doi:10.1145/357401.357402 ↗
- [3]Ravi S. Sandhu, Edward J. Coyne, Hal L. Feinstein, Charles E. Youman, “Role-Based Access Control Models”, Computer, vol. 29, no. 2, pp. 38-47, 1996doi:10.1109/2.485845 ↗
- [4]Vincent C. Hu et al., “Guide to Attribute Based Access Control (ABAC) Definition and Considerations”, NIST Special Publication 800-162, 2014doi:10.6028/NIST.SP.800-162 ↗
- [5]Ruoming Pang et al., “Zanzibar: Google's Consistent, Global Authorization System”, USENIX Annual Technical Conference, 2019
- [6]Scott Rose, Oliver Borchert, Stu Mitchell, Sean Connelly, “Zero Trust Architecture”, NIST Special Publication 800-207, 2020doi:10.6028/NIST.SP.800-207 ↗