elsa-workflows/elsa-core · error · InvalidOperationException

Secret ' ' is revoked and cannot be rotated.

Error message

Secret '{secret.Name}' is revoked and cannot be rotated.

What it means

Thrown by DefaultSecretManager.RotateAsync when the target secret's status is Revoked. Revocation is terminal in this model: a revoked secret cannot receive new versions, so any rotation attempt is rejected with this InvalidOperationException.

Solutions

  1. Check secret.Status before rotating and skip (or surface as blocked) when it is Revoked.
  2. If the secret must live again, create a new secret with CreateAsync (or a new name) rather than rotating the revoked one.
  3. Filter rotation jobs to Status == Active using ListAsync with a status filter.
  4. Catch InvalidOperationException and log a clear skip/recreate action.

Example fix

// before
await manager.RotateAsync(name, rotateRequest);
// after
var secret = await manager.GetAsync(name);
if (secret is { Status: SecretStatus.Active })
    await manager.RotateAsync(name, rotateRequest);
else
    logger.LogInformation("Skipping rotation; secret {Name} is not active.", name);
Defensive patterns

Strategy: validation

Validate before calling

var secret = await manager.GetAsync(name);
bool canRotate = secret is { Status: SecretStatus.Active };

Type guard

bool IsRotatable(Secret? s) => s is { Status: SecretStatus.Active };

Try / catch

try { await manager.RotateAsync(name, request); }
catch (InvalidOperationException e) when (e.Message.Contains("is revoked")) { /* skip or create replacement secret */ }

Prevention

When it happens

Trigger: Calling ISecretManager.RotateAsync(name, request) for a secret previously revoked via RevokeAsync (Status == SecretStatus.Revoked).

Common situations: Automated rotation jobs that do not check status and encounter secrets revoked after a security incident or decommissioning; dashboards offering rotate on revoked secrets.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/ac2c9a10e873399d. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.Secrets/Services/DefaultSecretManager.cs:66

    }

    public async Task<Secret> UpdateAsync(string name, UpdateSecretRequest request, CancellationToken cancellationToken = default)
    {
        var secret = await GetExistingAsync(name, cancellationToken);

        secret.DisplayName = string.IsNullOrWhiteSpace(request.DisplayName) ? secret.Name : request.DisplayName.Trim();
        secret.Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim();
        secret.UpdatedAt = DateTimeOffset.UtcNow;

        await repository.SaveAsync(secret, cancellationToken);
        return secret;
    }

    public async Task<Secret> RotateAsync(string name, RotateSecretRequest request, CancellationToken cancellationToken = default)
    {
        var secret = await GetExistingAsync(name, cancellationToken);
        if (secret.Status == SecretStatus.Revoked)
            throw new InvalidOperationException($"Secret '{secret.Name}' is revoked and cannot be rotated.");

        var provider = typeRegistry.Get(secret.TypeName);
        if (!provider.ValidateRotation(request, secret.StoreName, out var error))
            throw new InvalidOperationException(error);

        var store = storeRegistry.Get(secret.StoreName);
        EnsureCanWrite(store);
        var nextVersion = secret.Versions.Count == 0 ? 1 : secret.Versions.Max(x => x.Version) + 1;
        var version = new SecretVersion { Version = nextVersion, ExpiresAt = request.ExpiresAt };
        version.Payload = await store.WriteAsync(secret, version, CreatePayload(request), cancellationToken);

        foreach (var activeVersion in secret.Versions.Where(x => x.Status == SecretStatus.Active))
            activeVersion.Status = SecretStatus.Retired;

        secret.Versions.Add(version);
        secret.Status = SecretStatus.Active;
        secret.UpdatedAt = DateTimeOffset.UtcNow;
        await repository.SaveAsync(secret, cancellationToken);

View on GitHub (pinned to fe9217bdfa)