In a multi-tenant SaaS platform, the first design debate is usually "shared database or a database per tenant". Whichever you choose, there is an easier surface to overlook: the database permission boundary around administrative identities — especially the line between "a tenant's business administrator" and "the platform's database cluster". We ran a systemic convergence; the process is more worth recording than the conclusion.
Where the risk actually lives
Breaking "a tenant admin might reach beyond the database" into concrete categories is what makes it testable:
- Cross-database reads and writes: tenant-side identities connecting to other tenants' databases or the control plane, reading data that is not theirs;
- Object-level damage: structure operations like create/drop — one slip is a data incident;
- Session-level interference: terminating sessions in other databases (yanking someone else's connections);
- Permission creep: granting themselves or others more, bypassing the defined boundary.
Of the four, the truly dangerous pair is 1 and 3 — neither needs malice; one hard-coded operations script is enough to cause cross-tenant impact.
Step one: draw the permission matrix
Convergence starts with an inventory: list every identity that touches a database (application processes, operations scripts, administrators at every level), then every database (each tenant's, the control plane, the platform), marking each cell with what is actually needed — read, write, create tables, drop tables, open sessions, terminate sessions.
With the matrix drawn, the problems are plain: several identities that should act only inside their own database held cross-database read/write — even drop — permissions, because they shared one high-privilege account. "Reusing the privileged account" is the classic convenience debt of multi-tenant systems.
Step two: tighten to least privilege, category by category
One rule: every identity gets, per database, exactly what it needs and nothing more. In practice, by category:
| Identity class | Allowed | Not allowed |
|---|---|---|
| Tenant-side business admins | Business operations inside their tenant database | Any database-level action, any cross-database access |
| Read-only inspection scripts | Metadata and metrics reads | Writes, session termination |
| Change-execution scripts | Declared changes in listed databases | Anything unlisted |
| Platform-level operations | Controlled entry points, fully logged | Interactive access to production |
Two details get overlooked: separate inspection from change — routine checks must not carry write access, or one distracted script becomes an incident; and grant session capabilities explicitly — terminating sessions is not default baggage on a business identity; grant it deliberately, audit it separately.
Step three: verify by independent review, not "config changed"
The danger with permission work is stopping at "the config changed". Every "must not" cell got a test: perform the forbidden operation as that identity and require an explicit denial — not merely "it happened to be untried". On top of the tests, two more layers:
- Independent review: someone who did not implement the change re-runs the matrix tests — self-certification does not count;
- An acceptance baseline: the matrix and its tests join the regression list; new identities or databases pass the matrix before shipping, and periodic re-runs keep permissions from creeping back during iteration.
Takeaways
Multi-tenant isolation is not a switch; it is a permission matrix that needs maintaining. Treat "who can touch which database" as part of the product — every boundary tested, reviewed and regression-checked — far cheaper than patching holes after the tenant count grows.
