Trust

Tenant isolation

This is the control that matters most and the one most often described in a way that cannot be checked. Isolation in Rydya is not a filter the application remembers to apply. It is a property of the database, enforced below the code, and there is a structural audit that fails the build rather than a paragraph asking you to trust us.

  • Quality and compliance
  • Hospital operations
  • Clinical engineering

What is tenant isolation?

Definition

Tenant isolation

Tenant isolation is the guarantee that one organisation cannot read or affect another organisation's data, enforced at the database row rather than by application code choosing to filter, so that it holds even for a query nobody reviewed.

The distinction between those two is the whole subject. Almost every product filters by tenant in its queries, and almost every product means it. The question is what happens on the day somebody writes a query and forgets, or a new endpoint ships without the filter, or an integration calls something directly. If the only thing standing between two hospitals is a developer remembering a WHERE clause, isolation is a habit rather than a control.

Row level security moves it below the habit. The database refuses rather than the application declining, and it refuses identically whether the query came from a reviewed code path, an unreviewed one, or a mistake.

How the layers stack

Each one holds when the one above it fails. That is the whole design, and it is hard to see in a list.

Three stacked layers. The interface hides what you may not do, described as a convenience and never the boundary. Below it, the server checks every protected action, which holds if the interface is bypassed. Below that, row level security forced on every tenant table, which holds even if the server check was never written. Requests from a person, an integration and an unreviewed query all arrive at the stack, and a cross-tenant read is refused at the database. A note records that this is not yet proven under a transaction pooler.RequestCarries org idRow-level securityEnforced at the DBOnly your rowsBy construction
Defence in depth: the database refuses rather than the application declining.

How isolation is enforced, layer by layer

Four layers, and the important thing about them is that the lower ones do not depend on the upper ones being right.

Every tenant table carries an organisation id

Not as a convention but as a column the security policy reads. The organisation context is resolved on the server from the authenticated principal and is never taken from the client, because a client-supplied organisation is a request to be trusted rather than a fact.

Row level security is enabled and forced

Forced matters. Without it, the table owner bypasses the policy, which is a footgun that has produced real breaches in real products. Forcing it means the policy applies to everyone.

Server-side permission checks sit above it, deny by default

Every protected action is checked before it happens, independently of what the interface chose to render. Hiding a button is a convenience and is never the boundary. The absence of a grant is a denial rather than an unknown.

A structural audit fails the build

A whole-schema scan looks for tables missing row level security or FORCE, grants to anonymous or public roles, privileged functions with an unpinned search path, and whether the application role holds BYPASSRLS or superuser. It exits non-zero on any critical or high finding. It reports zero against the shipped schema.

The boundary covers more than the database

Storage uses organisation-prefixed keys. Search, exports, caches, notifications, API keys, webhooks and the audit trail are all inside it. A scheduled report is not a way to receive data you could not otherwise see, and neither is a notification.

Why we run a structural audit rather than writing tests for it

Because a test proves that the cases somebody thought of are isolated, and a schema scan proves that no table was missed.

Behavioural tests are necessary and we have them: a matrix of denial scenarios, each mapped to a named test. But they share a weakness with every test suite, which is that they cover what an author imagined. The table added last week by someone in a hurry is exactly the table nobody wrote a cross-tenant test for, and it is exactly the table that will leak.

The structural scan asks a different question: not "is this case denied" but "does any table in this schema lack the control". That question is answerable exhaustively, and it keeps being answerable as the schema grows, without anyone remembering to extend it. It is the difference between checking the doors you know about and counting the doors.

It is also the reason we can describe this control precisely rather than reassuringly. The claim is not that we are careful. It is that a build fails if a table is missing the policy, and you can read the scanner.

Where the boundary is deliberately crossed, and why

A small, enumerated set of privileged functions exists. Pretending otherwise would be the kind of omission that makes a reviewer distrust everything else on the page.

Organisation bootstrap

Creating an organisation happens before any tenant context exists, so it cannot run under one. It is a privileged function by necessity rather than convenience.

Invitation and public-report token resolution

An invitee is not yet a member, and a person scanning a QR code to report a fault has no account at all. Resolving those tokens has to happen outside a tenant session by definition.

Inbound service-account lookup and cross-organisation summaries

Authenticating a machine identity, and the platform console's aggregate views. The console sees summaries; it has no impersonation and never assumes a tenant user's identity.

Every one of them pins its search path, and the gate enforces that

An unpinned search path on a privileged function is a known escalation route. The structural audit fails the build if one appears, which is what makes this list safe to publish rather than a map for an attacker.

What we have not yet proven, stated precisely

Our isolation decision under transaction pooling is provisionally resolved rather than resolved: the live pooler test has not been run, and it is a pre-pilot gate. This matters more than it sounds. Session-scoped context under a transaction pooler is exactly the failure mode that would silently void isolation while every test still passed, so we will not describe this as validated in production conditions until it has been. Separately, the schema scan covers ordinary tables and does not cover views or materialised views. Both are real limits and neither is a reason to stop reading; they are the reason to ask us about them.

Who should check this, and what to ask

Your security people, and the question that separates real isolation from a filter takes one sentence.

Ask any vendor, us included: if a developer writes a query and forgets the tenant filter, what happens? There are only two honest answers. Either the database refuses, or the query returns another customer's data and the only defence was that nobody made that mistake. The second answer is extremely common and it is almost never volunteered.

The follow-up is just as short: is row level security forced, or merely enabled? Enabled-but-not-forced means the table owner bypasses the policy, which reads as protection and is not. If a vendor does not know the difference, that is the answer to your question.

Then ask what proves it stays true as the schema grows. Tests prove the cases somebody thought of. Ask whether anything counts the tables.

Questions

What stops one hospital seeing another hospital's data?

PostgreSQL row level security, enabled and forced, on every tenant table, with the organisation context resolved on the server from the authenticated principal and never taken from the client. Server-side permission checks sit above that, deny by default. The lower layer does not depend on the upper one being right: the database refuses even for a query nobody reviewed.

What happens if a developer forgets the tenant filter in a query?

The database refuses. This is the question worth asking every vendor in this category, because there are only two honest answers and the other one is that the query returns another customer's data and the only defence was that nobody made that mistake. That answer is common and almost never volunteered.

Do any functions bypass row level security?

Yes, a small enumerated set, by necessity: organisation bootstrap runs before a tenant context exists, invitation and public fault-report tokens belong to people who are not members, and machine-identity lookup plus platform summaries need cross-tenant reads. Every one pins its search path, and the structural audit fails the build if any of them does not, which is what makes the list safe to publish.

Has your isolation been validated in production conditions?

No, and we will not say otherwise. The decision on running row level security under a transaction pooler is provisionally resolved: the live pooler test is a pre-pilot gate that has not been run. Session-scoped context under a transaction pooler is precisely the failure mode that would void isolation while every test still passed, so the honest answer is that this is verified structurally and not yet under the pooler.

See it on your equipment

Live in an afternoon, useful the same week. A person replies, usually within one working day.

Contact us