Why isolation is the first requirement
A multi-tenant platform serving government agencies and regulated enterprises has one non-negotiable property: data belonging to one tenant must never be readable or writable by another. Every other feature is secondary to that. If isolation fails, no amount of workflow sophistication or AI assistance matters.
The difficulty is that isolation is a property of the whole system, not of one component. It can be broken by a missing filter in a query, a background job that iterates across tenants, a cache keyed without the tenant, an export that resolves a record by identifier alone, or an AI prompt that includes context from the wrong workspace. Any one of these is a plausible mistake for a careful engineer to make. The question is how to design so that the mistake is either impossible or caught before it ships.
Row-level isolation as the enforcement layer
Peek Platform stores every tenant-owned row with a tenant identifier and enforces isolation at the database layer with row-level policies. A request establishes its tenant context once, at the edge, from the authenticated session and the resolved host, and that context is set on the database connection for the duration of the request. Queries that do not match the context return no rows. Writes that would create or modify a row outside the context are rejected.
This means the application code does not have to remember to add a tenant filter to every query. Forgetting is the most common way isolation breaks, and moving the enforcement below the application removes that failure mode. Application-level filters still exist, but they are a convenience and a performance optimisation, not the line of defence.
Where the hard cases live
Row-level policies handle the ordinary request path. The interesting work is everywhere the ordinary path does not apply.
- Background worker: schedules, SLA escalations, email and webhooks run outside a user request. Each job is bound to exactly one tenant and sets its context before touching data; a job that needs to span tenants is refused by construction.
- Host resolution: tenant subdomains and custom domains are resolved to a tenant before any application logic runs, and the result is cached per host, not per path.
- Sigma AI: the context assembled for a proposal is built from the same tenant-scoped connection, so a prompt cannot accidentally include another workspace's records.
- Exports and shared links: any identifier presented from outside is resolved inside the tenant context; an identifier that belongs to another tenant resolves to nothing.
- Platform operators: administrative tooling uses a separate, audited path with explicit tenant selection, never a global bypass.
The isolation suite
Enforcement is necessary but not sufficient, because enforcement can regress. Peek Platform ships an isolation suite that runs on every change to the platform and can be run by a tenant against their own workspace. The suite creates two or more tenants with overlapping data shapes, then attempts to cross the boundary through every path it knows about.
The tests are adversarial by design. They do not check that the happy path works; they check that the wrong path fails. A test that passes because a query returned the expected record is weak. A test that passes because a query for another tenant's record returned nothing, and a write to it was rejected, and the rejection was logged, is the kind of test the suite is made of.
- Direct reads and writes by identifier across tenants through the API.
- List and search endpoints with filters crafted to match another tenant's data.
- Background jobs scheduled in one tenant attempting to touch records in another.
- Host spoofing: presenting a tenant host header with a session from a different tenant.
- Sigma context assembly: verifying that proposals only reference records from the current tenant.
- Export and shared-link resolution using identifiers from another tenant.
What the ledger adds
Isolation tells you that a boundary held. The audit ledger tells you what happened at the boundary. Every rejected cross-tenant access is written to the hash-chained ledger with the tenant context, the target, the actor and the path. Because entries are chained, the record of the attempt cannot be quietly removed. An organisation can review these entries to distinguish a misconfigured integration from an actual probing attempt.
The ledger also records the isolation suite runs themselves: when the suite ran, against which platform version, and whether every case passed. A tenant asking whether isolation was verified on the version they are running can be answered from the ledger rather than from a policy document.
Running the suite as a tenant
Organisations on Team and Enterprise plans can run the isolation suite against their own workspace from the Console. It provisions a disposable shadow tenant, executes the cross-boundary cases against the live platform, records the results in the ledger and tears the shadow tenant down. The report lists every case, its outcome and the ledger entries it produced. We would rather a security team verify the property themselves than take our word for it.
What we do not claim
The isolation suite covers the boundaries we know about. It does not prove the absence of boundaries we have not thought of, and no test suite can. What it does is make regressions in the known boundaries impossible to ship unnoticed, and it gives tenants a repeatable, recorded way to confirm the property on the version they are running. When we add a new path that touches tenant data, adding its adversarial cases to the suite is part of the definition of done.
Key takeaways
- Background worker: schedules, SLA escalations, email and webhooks run outside a user request. Each job is bound to exactly one tenant and sets its context before touching data; a job that needs to span tenants is refused by construction.
- Host resolution: tenant subdomains and custom domains are resolved to a tenant before any application logic runs, and the result is cached per host, not per path.
- Sigma AI: the context assembled for a proposal is built from the same tenant-scoped connection, so a prompt cannot accidentally include another workspace's records.
- Exports and shared links: any identifier presented from outside is resolved inside the tenant context; an identifier that belongs to another tenant resolves to nothing.
- Platform operators: administrative tooling uses a separate, audited path with explicit tenant selection, never a global bypass.
- Direct reads and writes by identifier across tenants through the API.
- List and search endpoints with filters crafted to match another tenant's data.
- Background jobs scheduled in one tenant attempting to touch records in another.
- Host spoofing: presenting a tenant host header with a session from a different tenant.
- Sigma context assembly: verifying that proposals only reference records from the current tenant.
- Export and shared-link resolution using identifiers from another tenant.