elsa-workflows/elsa-core · error · InvalidOperationException

A value is required for encrypted secrets.

Error message

A value is required for encrypted secrets.

What it means

EncryptedSecretStore protects the secret value with ASP.NET DataProtection before persisting it, so a payload with a null Value cannot be encrypted. WriteAsync throws an InvalidOperationException when payload.Value is null. Only the value is protected; metadata is copied alongside the protected blob.

Solutions

  1. Ensure payload.Value is populated before writing to the encrypted store.
  2. For metadata-only updates, read the existing payload, retain/copy its protected value metadata, or re-supply the value.
  3. Validate in your service layer that Value is non-null when targeting encrypted stores.

Example fix

// before
var payload = new SecretPayload();
payload.Metadata["Environment"] = "Production";
await encryptedStore.WriteAsync(secret, version, payload, ct);
// after
var payload = new SecretPayload { Value = "s3cr3t" };
payload.Metadata["Environment"] = "Production";
await encryptedStore.WriteAsync(secret, version, payload, ct);
Defensive patterns

Strategy: validation

Validate before calling

if (payload.Value is null)
    throw new ArgumentException("Encrypted secret writes require a non-null Value.");

Type guard

bool IsWritable(SecretPayload p) => p.Value is not null;

Try / catch

try
{
    await encryptedStore.WriteAsync(secret, version, payload, ct);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("value is required"))
{
    logger.LogError("Cannot write secret {SecretId}: value missing.", secret.Id);
}

Prevention

When it happens

Trigger: Calling WriteAsync on EncryptedSecretStore with a SecretPayload whose Value property is null — e.g., a metadata-only update written through the encrypted store.

Common situations: Update flows that only change metadata and leave Value null; deserialization bugs producing a null value; clients written against ConfigurationSecretStore semantics (where Value may be optional) reused against the encrypted store.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.Secrets/Stores/EncryptedSecretStore.cs:19

namespace Elsa.Secrets.Stores;

public class EncryptedSecretStore(ISecretValueProtector protector) : ISecretStore
{
    private const string ProtectedValueKey = "protectedValue";

    public string Name => SecretStoreNames.Encrypted;

    public SecretStoreDescriptor Descriptor { get; } = new(
        SecretStoreNames.Encrypted,
        "Elsa Encrypted Store",
        "Stores values in Elsa-managed encrypted payloads.",
        SecretStoreCapabilities.Read | SecretStoreCapabilities.Write | SecretStoreCapabilities.Delete | SecretStoreCapabilities.Test | SecretStoreCapabilities.Versioned,
        false);

    public Task<SecretPayload> WriteAsync(Secret secret, SecretVersion version, SecretPayload payload, CancellationToken cancellationToken = default)
    {
        if (payload.Value == null)
            throw new InvalidOperationException("A value is required for encrypted secrets.");

        var protectedPayload = new SecretPayload();
        protectedPayload.Metadata[ProtectedValueKey] = protector.Protect(payload.Value);

        foreach (var item in payload.Metadata.Where(x => !string.Equals(x.Key, ProtectedValueKey, StringComparison.OrdinalIgnoreCase)))
            protectedPayload.Metadata[item.Key] = item.Value;

        return Task.FromResult(protectedPayload);
    }

    public Task<SecretPayload?> ReadAsync(Secret secret, SecretVersion version, CancellationToken cancellationToken = default)
    {
        if (!version.Payload.Metadata.TryGetValue(ProtectedValueKey, out var protectedValue))
            return Task.FromResult<SecretPayload?>(null);

        return Task.FromResult<SecretPayload?>(SecretPayload.FromValue(protector.Unprotect(protectedValue)));
    }

View on GitHub (pinned to fe9217bdfa)