Asset Tenant Isolation
Summary
Source-backed description of how Asset persistence separates tenant data. Isolation is application-only: it combines EF Core global query filters, explicit predicates on the backfill path, and trust in the tenant carried by request claims/headers. There is no database-enforced row-level security.
Audience
Backend engineers, database reviewers, architects, QA, support, operations, security reviewers, and product owners.
Overview
Tenancy is represented by an integer TenantId on each tenant-scoped entity. The current tenant is resolved from the request context, and read paths rely on EF global query filters to constrain results. Writes are gated by a required tenant. Messaging-infrastructure tables are deliberately excluded from filtering, and the backfill path bypasses filters while applying its own explicit tenant predicate.
Confirmed persistence behavior
- Tenant representation:
TenantIdis an integer on tenant-scoped entities. An optionalTenantKeystring is also carried on the aggregate. - Global query filters (EF): five entities carry a global query filter of the form
IsSuperAdmin || TenantId == CurrentTenantId, whereCurrentTenantId = TenantId ?? 0. The filtered entities are theAssetaggregate, assignment history, document references, audit logs, and timeline entries. - Tenant context resolution:
HttpTenantContext(implementingITenantContext) derives the tenant from claims or headers, with aTenantKeyfallback mapping to a tenant id. This is header/claim trust — the tenant is taken from the inbound request context. - Design-time / bypass context:
NullTenantContextreportsIsSuperAdmin = trueand a null tenant, deliberately bypassing filters. It is used at design-time, during migrations, and during seeding. - Command-side gating: writes call
RequireTenantId(), which throws when no tenant is present; the aggregate factory rejects a non-positive tenant id. - Unfiltered messaging tables: the outbox and processed-integration-event tables have no query filter — they are messaging infrastructure and are not tenant-filtered at the EF level.
- Seeding: seeding uses
IgnoreQueryFilters()to check and write its marker regardless of tenant. - Backfill: the backfill path runs under
NullTenantContext(filters bypassed) but applies an explicit predicateWhere(TenantId == t)per tenant, so tenant scoping on that path is a hand-written predicate rather than the EF filter. - SuperAdmin bypass: a
SuperAdminrole claim setsIsSuperAdmin, which satisfies the first disjunct of every filter and therefore bypasses all query filters (elevated-context bypass). - No database-enforced isolation: there is no row-level security in the database. Isolation is application-only — EF query filters plus explicit predicates plus trust in the request-supplied tenant.
Isolation mechanism boundaries
| Mechanism | What it is | Where it applies |
|---|---|---|
| EF global query filter | `IsSuperAdmin | |
| Explicit predicate | Hand-written Where(TenantId == t) | Backfill path (filters bypassed) |
| Header/claim trust | Tenant taken from request claims/headers with TenantKey fallback | HttpTenantContext resolution |
| Database-enforced | Row-level security / DB policy | None present |
| Net posture | Application-only isolation | Whole context |
Classification
Implemented application-level tenant filtering with a Transitional foundation posture (anonymous, tenant-scoped endpoints).
Requires confirmation
- Authorization governance over the anonymous, tenant-scoped endpoints requires confirmation.
- Whether database-enforced row-level security should back the application filters is not decided in source and requires confirmation.
- Operational controls around the
SuperAdminelevated-bypass claim require confirmation.
Diagram
Related Articles
See Also
Keywords
- Asset multi-tenancy
- Tenant Isolation
- Tenant isolation
Source References
microservices/src/asset-service/Infrastructure/AssetDbContext.csmicroservices/src/asset-service/Infrastructure/Persistence.csmicroservices/src/asset-service/Domain/Asset/Asset.csmicroservices/src/asset-service/Backfill/AssetBackfillRunner.cs
Revision Information
- Status: Draft
- Last reviewed: 2026-07-17
- Review cycle: Quarterly