{"record":{"id":"e4b939b493a82fdb","repo":"elsa-workflows/elsa-core","slug":"a-configuration-key-is-required","errorCode":null,"errorMessage":"A configuration key is required.","messagePattern":"A configuration key is required\\.","errorType":"validation","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Secrets/Stores/ConfigurationSecretStore.cs","lineNumber":22,"sourceCode":"namespace Elsa.Secrets.Stores;\n\npublic class ConfigurationSecretStore(IConfiguration configuration, IOptions<SecretsOptions> options) : ISecretStore\n{\n    private const string ConfigurationKeyMetadataName = \"configurationKey\";\n\n    public string Name => SecretStoreNames.Configuration;\n\n    public SecretStoreDescriptor Descriptor { get; } = new(\n        SecretStoreNames.Configuration,\n        \"Configuration\",\n        \"Reads values from application configuration without storing the value in Elsa.\",\n        SecretStoreCapabilities.Read | SecretStoreCapabilities.Write | SecretStoreCapabilities.Test,\n        true);\n\n    public Task<SecretPayload> WriteAsync(Secret secret, SecretVersion version, SecretPayload payload, CancellationToken cancellationToken = default)\n    {\n        if (!payload.Metadata.TryGetValue(ConfigurationKeyMetadataName, out var key) || string.IsNullOrWhiteSpace(key))\n            throw new InvalidOperationException(\"A configuration key is required.\");\n\n        return Task.FromResult(new SecretPayload { Metadata = new Dictionary<string, string>(payload.Metadata, StringComparer.OrdinalIgnoreCase) });\n    }\n\n    public Task<SecretPayload?> ReadAsync(Secret secret, SecretVersion version, CancellationToken cancellationToken = default)\n    {\n        if (!version.Payload.Metadata.TryGetValue(ConfigurationKeyMetadataName, out var key) || string.IsNullOrWhiteSpace(key))\n            return Task.FromResult<SecretPayload?>(null);\n\n        var configuredValue = configuration[$\"{options.Value.ConfigurationSectionName}:{key}\"] ?? configuration[key];\n        return configuredValue == null ? Task.FromResult<SecretPayload?>(null) : Task.FromResult<SecretPayload?>(SecretPayload.FromValue(configuredValue));\n    }\n\n    public Task DeleteAsync(Secret secret, CancellationToken cancellationToken = default) => Task.CompletedTask;\n\n    public async Task<bool> TestAsync(Secret secret, SecretVersion version, CancellationToken cancellationToken = default)\n    {\n        var payload = await ReadAsync(secret, version, cancellationToken);","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Secrets/Stores/ConfigurationSecretStore.cs#L4-L40","documentation":"ConfigurationSecretStore reads secrets from application configuration, so each write payload must carry a metadata entry naming the configuration key the secret maps to. When payload.Metadata lacks that key or it is blank, WriteAsync refuses the operation with an InvalidOperationException. No value is actually written to configuration by this store.","triggerScenarios":"Calling WriteAsync on ConfigurationSecretStore with a SecretPayload whose Metadata dictionary does not contain the ConfigurationKeyMetadataName key, or contains it with null/whitespace value.","commonSituations":"Building payloads generically from user input without enforcing the metadata key; UI clients that omit advanced metadata; migrating secrets from another store where the metadata key was named differently.","solutions":["Add the required configuration-key metadata entry to the payload before calling WriteAsync.","Validate payloads in your UI/API layer before dispatching writes to a configuration-backed store.","If configuration-backed writes are not desired, use EncryptedSecretStore instead, which stores values rather than configuration references."],"exampleFix":"// before\nvar payload = new SecretPayload { Value = value };\nawait store.WriteAsync(secret, version, payload, ct);\n// after\nvar payload = new SecretPayload { Value = value };\npayload.Metadata[ConfigurationSecretStore.ConfigurationKeyMetadataName] = \"MyApp:ApiKey\";\nawait store.WriteAsync(secret, version, payload, ct);","handlingStrategy":"validation","validationCode":"if (!payload.Metadata.TryGetValue(ConfigurationSecretStore.ConfigurationKeyMetadataName, out var key) || string.IsNullOrWhiteSpace(key))\n    throw new ArgumentException(\"Payload must specify the configuration key metadata before writing to ConfigurationSecretStore.\");","typeGuard":"bool HasConfigKey(SecretPayload p) =>\n    p.Metadata.TryGetValue(ConfigurationSecretStore.ConfigurationKeyMetadataName, out var k) && !string.IsNullOrWhiteSpace(k);","tryCatchPattern":"try\n{\n    await store.WriteAsync(secret, version, payload, ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message == \"A configuration key is required.\")\n{\n    logger.LogError(\"Refusing write: missing configuration key metadata for secret {SecretId}.\", secret.Id);\n}","preventionTips":["Always attach the configuration-key metadata when building payloads for this store.","Validate payload metadata in your service layer before dispatching writes.","Document the required metadata key for consumers of your API."],"tags":["secrets","configuration","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}