Skip to content

Fix default tenant data visibility leak by removing NullIfEmpty conversion#7229

Merged
sfmskywalker merged 26 commits intoenh/tenant-agnostic-entitiesfrom
copilot/sub-pr-7226-another-one
Feb 1, 2026
Merged

Fix default tenant data visibility leak by removing NullIfEmpty conversion#7229
sfmskywalker merged 26 commits intoenh/tenant-agnostic-entitiesfrom
copilot/sub-pr-7226-another-one

Conversation

Copy link
Contributor

Copilot AI commented Jan 31, 2026

Implement asterisk sentinel value for tenant-agnostic entities

This change introduces the asterisk character ("*") as a sentinel value to represent tenant-agnostic entities in the Elsa Workflows engine. It also refactors the ActivityRegistry to use a three-dictionary architecture for tenant isolation and updates EF Core query filters to include tenant-agnostic records.

Changes

  • ADR Update
    Replaces the concept of using null as the tenant ID for tenant-agnostic entities with the asterisk character ("*") as documented in
    doc/adr/0009-asterisk-sentinel-value-for-tenant-agnostic-entities.md.

  • Tenant ID Convention
    Introduces Tenant.AgnosticTenantId constant ("*") to represent tenant-agnostic entities and enforces its use in code.
    Tenant.DefaultTenantId remains "" for the default tenant.

  • ActivityRegistry Refactoring
    Implements a three-dictionary architecture in ActivityRegistry using:

    • _tenantRegistries (tenant-specific)
    • _agnosticRegistry (tenant-agnostic)
    • _manualActivityDescriptors (legacy)

    ActivityRegistry methods are updated to use the new architecture and prioritize tenant-specific descriptors.

  • EF Core Query Filter Update
    Modifies SetTenantIdFilter to include records where
    TenantId == dbContext.TenantId OR TenantId == "*".

  • Entity Handler Update
    Updates ApplyTenantId to preserve TenantId = "*" and only apply the current tenant ID to entities where TenantId == null.

  • Migration Removal
    Removes the ConvertNullTenantIdToEmptyString migration, as the approach to tenant-agnostic entities has changed.

  • Database Context Modification
    Updates ElsaDbContextBase and TenantAwareDbContextFactory to align with the new tenant ID handling strategy.

  • Testing Updates

    • Modifies ComponentTestTenantResolver to resolve to Tenant.DefaultTenantId.
    • Removes TestTenantResolver.

Impact

  • Behavioral Changes
    Entities with TenantId = "*" are now treated as tenant-agnostic and accessible across all tenants.
    Entities with TenantId = null are assigned to the current tenant.

  • Structural Changes
    The ActivityRegistry now uses a three-dictionary architecture for improved tenant isolation and performance.

  • Dependencies Affected
    Affects all modules and components that interact with multitenant entities (e.g., WorkflowDefinition, WorkflowInstance), especially those using ITenantAccessor or querying the database.

  • Breaking Changes
    Existing systems relying on null for tenant-agnostic entities require data migration.
    The "*" character is now reserved and cannot be used as a tenant ID.

  • Performance Implications
    Storing tenant-agnostic entities only once improves database efficiency.
    The new ActivityRegistry architecture potentially improves thread safety and reduces race conditions.

Copilot AI and others added 2 commits January 31, 2026 02:31
- Updated ElsaDbContextBase to use empty string for default tenant
- Updated ApplyTenantId to stop converting empty string to null
- Updated TenantAwareDbContextFactory to preserve empty string for default tenant
- Updated Store.cs to preserve empty string for default tenant
- This ensures: null = tenant-agnostic (visible to all), "" = default tenant

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
…qlServer

- Added Management migration to convert null to "" for WorkflowDefinitions and WorkflowInstances
- Added Runtime migration to convert null to "" for all runtime entities
- This ensures existing default tenant data is properly migrated per ADR-0008
- Note: Similar migrations needed for PostgreSql, MySql, Sqlite, and Oracle providers

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix null TenantId implementation for tenant-agnostic entities Fix default tenant data visibility leak by removing NullIfEmpty conversion Jan 31, 2026
Copilot AI requested a review from sfmskywalker January 31, 2026 02:41
@sfmskywalker sfmskywalker marked this pull request as ready for review January 31, 2026 07:52
Copilot AI review requested due to automatic review settings January 31, 2026 07:52
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical tenant data visibility leak in the persistence layer by removing NullIfEmpty() conversions that were incorrectly transforming empty string tenant IDs to null. According to ADR-0008 and ADR-0009, empty string ("") represents the default tenant (visible only to default tenant), while null represents tenant-agnostic entities (visible to all tenants). The previous conversion caused default tenant data to leak across all tenants.

Changes:

  • Removed .NullIfEmpty() conversions from persistence layer to preserve empty string tenant IDs
  • Added SQL Server database migrations to convert existing null TenantId values to empty strings (assumes pre-migration nulls are default tenant data)
  • Updated four persistence layer components: ElsaDbContextBase, ApplyTenantId, TenantAwareDbContextFactory, and Store.cs

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/modules/Elsa.Persistence.EFCore.Common/ElsaDbContextBase.cs Removed .NullIfEmpty() call in constructor when setting TenantId from tenant accessor
src/modules/Elsa.Persistence.EFCore.Common/EntityHandlers/ApplyTenantId.cs Removed .NullIfEmpty() call when applying tenant ID to entities before saving
src/modules/Elsa.Persistence.EFCore.Common/TenantAwareDbContextFactory.cs Removed .NullIfEmpty() call when setting tenant ID on created DbContext instances
src/modules/Elsa.Persistence.EFCore.Common/Store.cs Removed .NullIfEmpty() call in bulk upsert operations when assigning tenant ID
src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Runtime/20260131023442_ConvertNullTenantIdToEmptyString.cs Migration to convert null TenantId to empty string for 7 Runtime tables (ActivityExecutionRecords, BookmarkQueueItems, Bookmarks, KeyValuePairs, Triggers, WorkflowExecutionLogRecords, WorkflowInboxMessages)
src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Runtime/20260131023442_ConvertNullTenantIdToEmptyString.Designer.cs Auto-generated EF Core migration designer file for Runtime context
src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Management/20260131023442_ConvertNullTenantIdToEmptyString.cs Migration to convert null TenantId to empty string for 2 Management tables (WorkflowDefinitions, WorkflowInstances)
src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Management/20260131023442_ConvertNullTenantIdToEmptyString.Designer.cs Auto-generated EF Core migration designer file for Management context
Files not reviewed (2)
  • src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Management/20260131023442_ConvertNullTenantIdToEmptyString.Designer.cs: Language not supported
  • src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Runtime/20260131023442_ConvertNullTenantIdToEmptyString.Designer.cs: Language not supported

- Introduce `AgnosticTenantId` constant to manage tenant-agnostic entities.
- Modify entity handling logic to respect tenant-agnostic designations.
- Adjust workflow processing to include tenant-agnostic workflows.
- Update caching and activity descriptor logic to accommodate the `AgnosticTenantId`.
…or improved clarity and separation of tenant-specific and tenant-agnostic activity descriptors. Remove `TestTenantResolver` and update workflow definition handling for tenant support.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 31 changed files in this pull request and generated 12 comments.

Files not reviewed (2)
  • src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Management/20260131023442_ConvertNullTenantIdToEmptyString.Designer.cs: Language not supported
  • src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Runtime/20260131023442_ConvertNullTenantIdToEmptyString.Designer.cs: Language not supported

…ver tenant-agnostic and simplify descriptor retrieval logic.
…ties

Replace the previous convention of using `null` for tenant-agnostic entities with an asterisk (`"*"`) for improved clarity and system architecture. Updated ADR documentation, TOC, and dependency graph accordingly.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 34 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Management/20260131023442_ConvertNullTenantIdToEmptyString.Designer.cs: Language not supported

sfmskywalker and others added 8 commits February 1, 2026 14:55
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Document the tenant ID flow from entity creation to query, emphasizing normalization and tenant-agnostic workflows. Update semantic flow diagrams and provide testing considerations for preserving `"*"` values in multi-tenant scenarios.
Enhance Architecture Decision Record to detail explicit requirements for tenant-agnostic database entities, highlighting differences between in-memory activity descriptors and persistent entities. Emphasize importance of setting `TenantId = "*"` to prevent accidental data leakage.
…agnostic IDs, reducing redundant processing.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 23 changed files in this pull request and generated 7 comments.

Files not reviewed (1)
  • src/modules/Elsa.Persistence.EFCore.SqlServer/Migrations/Management/20260131023442_ConvertNullTenantIdToEmptyString.Designer.cs: Language not supported

@sfmskywalker sfmskywalker force-pushed the copilot/sub-pr-7226-another-one branch from 94d4506 to de0f654 Compare February 1, 2026 20:11
sfmskywalker and others added 5 commits February 1, 2026 21:11
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@sfmskywalker sfmskywalker merged commit ddffc29 into enh/tenant-agnostic-entities Feb 1, 2026
2 checks passed
@sfmskywalker sfmskywalker deleted the copilot/sub-pr-7226-another-one branch February 1, 2026 21:11
sfmskywalker added a commit that referenced this pull request Feb 2, 2026
* Add ADR for Null Tenant ID, implement tenant-agnostic logic

Introduce ADR-0009 to document the use of `null` for tenant-agnostic entities, enhancing multitenancy handling. Update multitenancy features across the codebase, including EF Core query filters and ActivityRegistry, to handle null as a tenant ID, ensuring tenant-agnostic entities are accessible across all tenants.

* Add multitenancy support in `ActivityTestFixture` by registering `ITenantAccessor`.

* Update src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove unused `using Elsa.Common.Multitenancy;` from WorkflowDefinitionActivityDescriptorFactory (#7230)

* Initial plan

* Remove unused using Elsa.Common.Multitenancy statement

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Optimize ActivityRegistry.Find to prefer tenant-specific descriptors without performance regression (#7227)

* Initial plan

* Optimize Find(string type) to prefer tenant-specific descriptors with single-pass iteration

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Apply review feedback: combine if statements and add comprehensive unit tests

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Refactor tests for DRYness using theories and helper methods

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Clean up extra whitespace in test file

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Fix default tenant data visibility leak by removing NullIfEmpty conversion (#7229)

* Initial plan

* Remove NullIfEmpty conversion to align with ADR-0008 and ADR-0009

- Updated ElsaDbContextBase to use empty string for default tenant
- Updated ApplyTenantId to stop converting empty string to null
- Updated TenantAwareDbContextFactory to preserve empty string for default tenant
- Updated Store.cs to preserve empty string for default tenant
- This ensures: null = tenant-agnostic (visible to all), "" = default tenant

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Add database migration to convert null TenantId to empty string for SqlServer

- Added Management migration to convert null to "" for WorkflowDefinitions and WorkflowInstances
- Added Runtime migration to convert null to "" for all runtime entities
- This ensures existing default tenant data is properly migrated per ADR-0008
- Note: Similar migrations needed for PostgreSql, MySql, Sqlite, and Oracle providers

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Clarify tenant handling logic in `ElsaDbContextBase` with new ADR references.

* Add tenant ID convention analysis documents and quick reference guide

* Implement tenant-agnostic functionality across modules

- Introduce `AgnosticTenantId` constant to manage tenant-agnostic entities.
- Modify entity handling logic to respect tenant-agnostic designations.
- Adjust workflow processing to include tenant-agnostic workflows.
- Update caching and activity descriptor logic to accommodate the `AgnosticTenantId`.

* Refactor tenant management and registry logic in `ActivityRegistry` for improved clarity and separation of tenant-specific and tenant-agnostic activity descriptors. Remove `TestTenantResolver` and update workflow definition handling for tenant support.

* Refactor `ActivityRegistry`: prioritize tenant-specific descriptors over tenant-agnostic and simplify descriptor retrieval logic.

* Improve async handling in `CommandHandlerInvokerMiddleware` to await tasks without blocking

* Update ADR to use asterisk as sentinel value for tenant-agnostic entities

Replace the previous convention of using `null` for tenant-agnostic entities with an asterisk (`"*"`) for improved clarity and system architecture. Updated ADR documentation, TOC, and dependency graph accordingly.

* Remove migration `ConvertNullTenantIdToEmptyString` and its associated designer file to clean up the codebase.

* Refactor `ActivityRegistry`: streamline activity descriptor removal logic and simplify tenant ID checks.

* Update Elsa.sln

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Simplify `RefreshDescriptorsAsync` by removing unnecessary local variable `currentTenantId`.

* Remove unused `currentTenantId` variable from `ActivityRegistry`.

* Add detailed semantic flow and key points to ADR 0009

Document the tenant ID flow from entity creation to query, emphasizing normalization and tenant-agnostic workflows. Update semantic flow diagrams and provide testing considerations for preserving `"*"` values in multi-tenant scenarios.

* Remove outdated Tenant ID Analysis and associated documents

* Add security-by-default design for tenant-agnostic entities in ADR

Enhance Architecture Decision Record to detail explicit requirements for tenant-agnostic database entities, highlighting differences between in-memory activity descriptors and persistent entities. Emphasize importance of setting `TenantId = "*"` to prevent accidental data leakage.

* Normalize tenant ID grouping in `ActivityRegistry` to unify null and agnostic IDs, reducing redundant processing.

* Refactor `SignalManager`: improve timeout handling and streamline signal task cancellation.

* Update src/modules/Elsa.Workflows.Core/Models/TenantRegistryData.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor tests to use `Tenant.AgnosticTenantId` instead of `null` for tenant-agnostic descriptors.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Enhance logging in recurring tasks: add error handling and logger support to prevent crashes in scheduled timers.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants