elsa-workflows/elsa-core · error · NotSupportedException
The VNext secrets persistence provider does not support…
Error message
The VNext secrets persistence provider does not support multitenancy, and the current tenant is '{tenantId}'. It stores secrets keyed by name alone, so tenants would share them. Use an Entity Framework Core secrets provider. What it means
The VNext secrets repository stores secrets keyed only by normalized name with no tenant discriminator, so it cannot isolate data between tenants. EnsureDefaultTenant is called by every read/write operation and throws NotSupportedException whenever the ambient tenant accessor reports a non-empty tenant id, preventing silent cross-tenant secret sharing.
Solutions
- Switch to the Entity Framework Core secrets provider (Elsa.Secrets.Persistence.EFCore), which supports tenant-scoped secrets, and run its migrations.
- If secrets are intentionally global, run this workload in single-tenant mode so tenantAccessor.TenantId is null/empty.
- Remove or bypass tenant assignment for the secrets module only if sharing secrets across tenants is acceptable — generally not recommended.
- Audit configuration: ensure the DI registration matches the tenancy mode documented for the deployment.
Example fix
// before services.AddSecretsFeature().UseVNextPersistence(); // with multitenancy enabled // after services.AddSecretsFeature().UseEntityFrameworkCorePersistence(); // tenant-aware store
Defensive patterns
Strategy: try-catch
Validate before calling
var tenantId = tenantAccessor?.TenantId;
if (!string.IsNullOrEmpty(tenantId) && usingVNextSecretsProvider)
throw new InvalidOperationException("Configure the EF Core secrets provider; VNext secrets do not support multitenancy."); Try / catch
try
{
await repository.GetAsync(name, cancellationToken);
}
catch (NotSupportedException ex) when (ex.Message.Contains("does not support multitenancy"))
{
logger.LogCritical(ex, "Secrets provider incompatible with multi-tenant mode; switch to EF Core provider.");
throw;
} Prevention
- Match the secrets persistence provider to the tenancy mode: VNext = single-tenant only.
- Validate DI configuration at startup by probing with an ambient tenant set.
- Document that enabling multitenancy requires the EF Core secrets provider.
When it happens
Trigger: Any VNext repository operation (GetAsync, ListAsync, AddAsync, TryAddOrReplaceDeletedAsync, SaveAsync) executed in a multi-tenant host where the current request carries a tenant id — i.e. the app registered the VNext provider while tenancy is enabled.
Common situations: Enabling Elsa multitenancy after adopting the VNext secrets provider; a request scoped to a tenant (via header/token) hitting secrets APIs; environment misconfiguration where an EF Core secrets provider was intended but VNext was registered.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- A secret named ' ' already exists.
- A secret named ' ' already exists.
- 23505
- A configuration key is required.
- A managed secret writer returned the live secret reference…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/3a48b91d5121fe20.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Secrets.Persistence.VNext/Repositories/VNextSecretRepository.cs:37
/// the same document. Making it tenant-aware means changing the document id scheme, which relocates
/// existing documents and is a storage change to make deliberately rather than fold into a tenancy fix.
/// <para>
/// Until then this throws rather than quietly serving one tenant's secret to another. It is checked per
/// call rather than at startup so it catches a tenant context entered at runtime, and it stays silent for
/// the default tenant, which is every single-tenant deployment.
/// </para>
/// <para>
/// The accessor is optional because it is registered by the tenants module: a host that never added
/// multitenancy has none, and requiring it would break resolving this repository at all. No accessor
/// means no tenancy, which is the default tenant.
/// </para>
/// </remarks>
private void EnsureDefaultTenant()
{
var tenantId = tenantAccessor?.TenantId;
if (!string.IsNullOrEmpty(tenantId))
throw new NotSupportedException(
$"The VNext secrets persistence provider does not support multitenancy, and the current tenant is '{tenantId}'. "
+ "It stores secrets keyed by name alone, so tenants would share them. Use an Entity Framework Core secrets provider.");
}
public const string StorageUnitName = "Secrets";
private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web)
{
Converters = { new JsonStringEnumConverter() }
};
public async Task<Secret?> GetAsync(string normalizedName, CancellationToken cancellationToken = default)
{
EnsureDefaultTenant();
var document = await documentStore.LoadAsync(StorageUnitName, NormalizeDocumentId(normalizedName), cancellationToken);
return document is null ? null : Deserialize(document);
}
View on GitHub (pinned to fe9217bdfa)