Skip to content

Fix cross-tenant cache collision in CachingWorkflowDefinitionService#7318

Merged
sfmskywalker merged 2 commits intorelease/3.6.0from
copilot/fix-cache-collision-issue
Feb 23, 2026
Merged

Fix cross-tenant cache collision in CachingWorkflowDefinitionService#7318
sfmskywalker merged 2 commits intorelease/3.6.0from
copilot/fix-cache-collision-issue

Conversation

Copy link
Contributor

Copilot AI commented Feb 23, 2026

In multi-tenant deployments, WorkflowDefinitionCacheManager generated cache keys with no tenant scope, causing CachingWorkflowDefinitionService to serve cached workflow definitions from one tenant to another when workflows share the same name or definition ID.

Changes

  • WorkflowDefinitionCacheManager: Injected ITenantAccessor and added a TenantPrefix() helper that prepends "{tenantId}:" to every generated cache key. When no tenant is active (single-tenant), the prefix is empty — no behavioral change.

This mirrors the isolation pattern already applied in CachingWorkflowDefinitionStore:

private string TenantPrefix()
{
    var tenantId = tenantAccessor.TenantId;
    return string.IsNullOrEmpty(tenantId) ? string.Empty : $"{tenantId}:";
}

All Create*CacheKey methods — including definition version keys, filter-based keys, and change token keys — are now tenant-scoped.

Original prompt

This section details on the original issue you should resolve

<issue_title>CachingWorkflowDefinitionService Missing Tenant Isolation Causes Cross-Tenant Workflow Cache Collision in Multi-Tenant Deployments</issue_title>
<issue_description>When using management.UseCache() in a multi-tenant ELSA setup, CachingWorkflowDefinitionService does not scope its cache keys by tenant. This causes workflow definitions from one tenant to be served to a different tenant when both tenants have workflows with the same name (e.g. "ChatBot"). The only workaround currently is to disable caching entirely.

Environment

Component: Elsa.Workflows.Management
File: src/modules/Elsa.Workflows.Management/Services/CachingWorkflowDefinitionService.cs
Triggered by: management.UseCache() → CachingWorkflowDefinitionsFeature.Apply() → Services.Decorate<IWorkflowDefinitionService, CachingWorkflowDefinitionService>()

Steps to Reproduce

Set up ELSA with multi-tenancy enabled and management.UseCache() called in Program.cs
Create two tenants — e.g. tenant-A and tenant-B
In each tenant, create a workflow with the same name — e.g. "ChatBot" — but with different activities and logic
Trigger tenant-A's chatbot workflow
Trigger tenant-B's chatbot workflow

Expected: Each tenant runs their own isolated "ChatBot" workflow.
Actual: tenant-B receives tenant-A's cached WorkflowGraph — the wrong workflow executes. Disabling cache fixes the issue.

Root Cause
CachingWorkflowDefinitionService is registered as a decorator over IWorkflowDefinitionService via CachingWorkflowDefinitionsFeature. Its internal GetFromCacheAsync method builds cache keys with no tenant prefix, and the class does not inject ITenantAccessor at all:</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix caching workflow definition service for tenant isolation Fix cross-tenant cache collision in CachingWorkflowDefinitionService Feb 23, 2026
Copilot AI requested a review from sfmskywalker February 23, 2026 11:58
@sfmskywalker sfmskywalker marked this pull request as ready for review February 23, 2026 12:22
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 23, 2026

Greptile Summary

Adds tenant-scoped cache key prefixes to WorkflowDefinitionCacheManager to prevent cross-tenant workflow cache collisions in multi-tenant deployments.

  • Injected ITenantAccessor into the constructor to access current tenant context
  • Added TenantPrefix() helper method that prepends {tenantId}: to cache keys when a tenant is active
  • Applied tenant prefix to all 7 cache key generation methods (CreateWorkflowDefinitionVersionCacheKey, CreateWorkflowDefinitionFilterCacheKey, CreateWorkflowVersionCacheKey, CreateWorkflowFilterCacheKey, CreateWorkflowDefinitionChangeTokenKey)
  • Mirrors the tenant isolation pattern already implemented in CachingWorkflowDefinitionStore (line 140-142)

The fix ensures that CachingWorkflowDefinitionService (which uses these cache keys) no longer serves cached workflows from one tenant to another when workflows share the same name or definition ID.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation correctly follows the established tenant isolation pattern from CachingWorkflowDefinitionStore, applies the prefix consistently to all cache key methods, and gracefully handles single-tenant scenarios (returns empty prefix when no tenant is active). The change is minimal, focused, and directly addresses the reported cross-tenant cache collision issue without introducing new dependencies or side effects.
  • No files require special attention

Important Files Changed

Filename Overview
src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionCacheManager.cs Added tenant isolation to cache keys by injecting ITenantAccessor and prefixing all cache keys with {tenantId}: when a tenant is active

Sequence Diagram

sequenceDiagram
    participant Client as Client Request
    participant Service as CachingWorkflowDefinitionService
    participant CacheMgr as WorkflowDefinitionCacheManager
    participant TenantAccessor as ITenantAccessor
    participant Cache as ICacheManager

    Client->>Service: FindWorkflowGraphAsync(definitionId, versionOptions)
    Service->>CacheMgr: CreateWorkflowVersionCacheKey(definitionId, versionOptions)
    CacheMgr->>TenantAccessor: Get TenantId
    TenantAccessor-->>CacheMgr: Returns "tenant-A"
    CacheMgr->>CacheMgr: TenantPrefix() → "tenant-A:"
    CacheMgr-->>Service: Returns "tenant-A:Workflow:definitionId:versionOptions"
    Service->>Cache: GetOrCreateAsync(key)
    alt Cache Hit
        Cache-->>Service: Return cached WorkflowGraph for tenant-A
    else Cache Miss
        Service->>Service: decoratedService.FindWorkflowGraphAsync()
        Service->>Cache: Store with tenant-scoped key
    end
    Service-->>Client: Return tenant-isolated WorkflowGraph
Loading

Last reviewed commit: 7d02d3d

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.

CachingWorkflowDefinitionService Missing Tenant Isolation Causes Cross-Tenant Workflow Cache Collision in Multi-Tenant Deployments

2 participants