Tenant isolation, and why it is not a permission check

Every multi-tenant product claims isolation. The question your security reviewer will ask is where it is enforced — and the honest answers range from “in each page” to “in the data layer, with a test that tries to get around it”.

Shipped in segment S00 · 831 words
ISOLATION IS A DATA-LAYER PREDICATE, NOT A CHECK IN A PAGERequestany route, any jobSession → contexttenantId · externalOrgId?AsyncLocalStoragenot a threaded parametertenantExtensioninjects the where clauseEvery queryrows · counts · facetsif externalOrgId is present, narrow againShared objects onlydirect ID fetch also failsA parameter can be forgotten at one call site with nothing failing. There is no parameter, so there is nothing to forget.

What it is, and what it is not

Tenant isolation in Manufacturing PLM is a predicate injected into every database query by a client extension, sourced from a request-scoped context. It is not a check written at the top of each route, and it is not a convention the team agrees to follow.

The difference is the failure mode. A check written per route fails silently at the one route where somebody forgot it, and the code looks completely normal — there is nothing to see, because the bug is an absence. Nothing in a code review reliably catches a missing line.

A predicate injected below the query builder has no per-route surface to forget. The tenant context is carried in async local storage rather than passed as a parameter, precisely because a parameter can be omitted at one call site with nothing failing.

The mechanism, end to end

A request arrives and its session establishes a context holding a tenant identifier and, for external users, an organisation identifier. That context lives in async local storage for the duration of the request or background job.

The database client extension reads the context on every operation and narrows the query. Rows, totals, facet counts and pagination all derive from one where clause — not three that happen to agree today. A total of twenty-nine above twenty-six visible rows announces the existence of three objects as loudly as listing them would, so they are not permitted to be computed from different sets.

Where a raw SQL query is genuinely needed — full-text ranking, for instance — it does not receive the extension's protection automatically, so it is intersected with a set of identifiers the ordinary client has already filtered. An access control rule buried inside a raw SQL string is an access control rule nobody reviews.

The failure it prevents

The incident that ends a manufacturing SaaS company is not a breach of the login page. It is a competitor's part number appearing in a customer's search results.

It usually arrives through a path nobody classified as sensitive: an autocomplete endpoint, an export job running outside a request, a report that aggregates across a table the ordinary screens never touch directly. Each of those is a place where a per-route check is easy to omit and impossible to notice.

The supplier portal is the sharpest version. External organisations hold real sessions against a real tenant, and the requirement is that a supplier cannot fetch an unshared part even by guessing its identifier. That is a negative — and negatives have to be proved, so there is a test that tries the direct fetch and asserts it fails.

How it meets the rest of the product

Search filters at query time rather than partitioning the index, which means a permission change takes effect immediately instead of requiring a re-index. Attribute-level access control does not partition at all, so index-time partitioning could not have covered it anyway.

Administrators are not special-cased. An administrator who needs into a restricted programme grants themselves access and leaves an audit row saying they did, rather than possessing a silent bypass that no report would ever show.

The audit trail itself is hash-chained: each row commits to the previous one, so a tampered or deleted entry breaks the chain and verification says where. Writes that arrive without an attributed actor are refused outright rather than recorded as unknown.

How it meets your ERP

Connector runs are jobs, not requests, and jobs are exactly where isolation usually leaks — a nightly sync typically holds broad credentials and iterates across tenants. In Manufacturing PLM a connector run establishes the same tenant context a user request would, so the same predicate applies to it.

Data leaving for the ERP carries only that tenant's rows by construction, and every sync is an attributed write in the audit trail naming the connector, the run and the direction. “Which system changed this value” has an answer that does not trail off into a shrug.

Credentials are held per tenant per connector and are never shared across tenants, including for the ViBe ERP native connector where both systems happen to be operated by the same company. Convenience is the usual reason this rule gets broken.

Where the boundary is

This is logical isolation on shared infrastructure. It is not single-tenant deployment, not on-premise, and not ITAR segregation — those are deliberately out of scope for this product, and a buyer who needs them should not be sold this one.

Data residency is likewise not yet regionalised. It is tracked as a decision to revisit for the first customer whose contract genuinely requires it, rather than claimed now and retrofitted under pressure later.

Stating both limits on a public page is deliberate. A buyer who needs on-premise deployment will find out eventually, and finding out during a procurement review costs both sides considerably more than reading it here in week one.

Facts

EnforcementData-layer predicate, injected by the client extension
ContextAsync local storage — no parameter to forget
ConsistencyRows, totals, facets and paging share one where
External usersSupplier sessions narrow a second time
Direct fetchUnshared object by ID fails — there is a test that tries
AdministratorsNo silent bypass; access is granted and audited
AuditHash-chained; unattributed writes are refused
Connector jobsRun inside the same tenant context as a request
Not offeredSingle-tenant, on-premise, ITAR segregation

Frequently asked

Where is isolation actually enforced?

In the database client extension, below every query the application makes. It is not a check at the top of each route, because that pattern fails silently at whichever route somebody forgot — and a missing line is invisible in code review in a way a wrong line is not.

Can a supplier see parts I have not shared?

No. An external session carries an organisation identifier that narrows queries a second time, on top of the tenant predicate. The requirement includes direct fetch by identifier, so guessing an ID does not help, and there is a test in the product that attempts exactly that and asserts failure.

Do result counts leak anything?

No, and this is a real leak in many systems. Results, totals, facet counts and pagination all come from one shared clause rather than being computed separately. A total higher than the visible rows would announce the existence of hidden objects just as effectively as listing them.

Are administrators exempt?

No. An administrator who needs access to a restricted programme grants it to themselves and leaves an audit row recording that they did. A silent super-user bypass is convenient right up to the compliance review where somebody asks who read the file and the answer is unavailable.

What about background jobs and syncs?

They establish the same tenant context a user request does, so the same predicate applies. Background work is where isolation usually leaks, because a nightly job typically runs with broad credentials and iterates across tenants — that shape does not exist here.

Can I get a single-tenant or on-premise deployment?

Not from this product. Manufacturing PLM is multi-tenant SaaS by design, and single-tenant, on-premise and ITAR-segregated deployment are explicitly out of scope. If your programme requires them, this is the wrong tool and we would rather say so before the evaluation than during it.

How would I verify any of this?

The security review pack contains the isolation design, the audit chain verification procedure, the access control model and the test list, including the negative tests. It is available without a call, because a reviewer who has to book a meeting to read a design document has already formed an opinion.