Skip to main content

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: TenantId is an integer on tenant-scoped entities. An optional TenantKey string is also carried on the aggregate.
  • Global query filters (EF): five entities carry a global query filter of the form IsSuperAdmin || TenantId == CurrentTenantId, where CurrentTenantId = TenantId ?? 0. The filtered entities are the Asset aggregate, assignment history, document references, audit logs, and timeline entries.
  • Tenant context resolution: HttpTenantContext (implementing ITenantContext) derives the tenant from claims or headers, with a TenantKey fallback mapping to a tenant id. This is header/claim trust — the tenant is taken from the inbound request context.
  • Design-time / bypass context: NullTenantContext reports IsSuperAdmin = true and 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 predicate Where(TenantId == t) per tenant, so tenant scoping on that path is a hand-written predicate rather than the EF filter.
  • SuperAdmin bypass: a SuperAdmin role claim sets IsSuperAdmin, 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

MechanismWhat it isWhere it applies
EF global query filter`IsSuperAdmin
Explicit predicateHand-written Where(TenantId == t)Backfill path (filters bypassed)
Header/claim trustTenant taken from request claims/headers with TenantKey fallbackHttpTenantContext resolution
Database-enforcedRow-level security / DB policyNone present
Net postureApplication-only isolationWhole 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 SuperAdmin elevated-bypass claim require confirmation.

Diagram

See Also

Keywords

  • Asset multi-tenancy
  • Tenant Isolation
  • Tenant isolation

Source References

  • microservices/src/asset-service/Infrastructure/AssetDbContext.cs
  • microservices/src/asset-service/Infrastructure/Persistence.cs
  • microservices/src/asset-service/Domain/Asset/Asset.cs
  • microservices/src/asset-service/Backfill/AssetBackfillRunner.cs

Revision Information

  • Status: Draft
  • Last reviewed: 2026-07-17
  • Review cycle: Quarterly