{"record":{"id":"cb50ef72c01af9de","repo":"elsa-workflows/elsa-core","slug":"configuration-connection-connectionkey-supplies-secret-field","errorCode":null,"errorMessage":"Configuration connection '{connectionKey}' supplies secret field '{name}' through AdapterSettings. Configure it through SecretBindings instead.","messagePattern":"Configuration connection '(.+?)' supplies secret field '(.+?)' through AdapterSettings\\. Configure it through SecretBindings instead\\.","errorType":"validation","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication/Services/AdapterSettingsSecretFieldGuard.cs","lineNumber":20,"sourceCode":"using Elsa.ExternalAuthentication.Models;\n\nnamespace Elsa.ExternalAuthentication.Services;\n\n/// <summary>\n/// Prevents adapter setting fields that are declared as secret bindings from being persisted or returned as ordinary settings.\n/// </summary>\npublic static class AdapterSettingsSecretFieldGuard\n{\n    /// <summary>Throws when a descriptor-declared secret is supplied through an adapter settings document.</summary>\n    public static void ThrowIfContainsDeclaredSecret(JsonElement settings, ExternalAuthenticationAdapterDescriptor descriptor, string connectionKey)\n    {\n        if (settings.ValueKind != JsonValueKind.Object)\n            return;\n\n        var names = GetSecretFieldNames(descriptor);\n        var name = names.FirstOrDefault(name => settings.TryGetProperty(name, out _));\n        if (name is not null)\n            throw new InvalidOperationException($\"Configuration connection '{connectionKey}' supplies secret field '{name}' through AdapterSettings. Configure it through SecretBindings instead.\");\n    }\n\n    /// <summary>Returns a settings document with descriptor-declared secret fields redacted.</summary>\n    public static JsonElement RedactDeclaredSecrets(JsonElement settings, ExternalAuthenticationAdapterDescriptor descriptor)\n    {\n        if (settings.ValueKind != JsonValueKind.Object)\n            return settings.ValueKind == JsonValueKind.Undefined ? default : settings.Clone();\n\n        var names = GetSecretFieldNames(descriptor);\n        if (names.Count == 0 || !names.Any(name => settings.TryGetProperty(name, out _)))\n            return settings.Clone();\n\n        using var stream = new MemoryStream();\n        using (var writer = new Utf8JsonWriter(stream))\n        {\n            writer.WriteStartObject();\n            foreach (var property in settings.EnumerateObject())\n            {","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication/Services/AdapterSettingsSecretFieldGuard.cs#L2-L38","documentation":"Elsa's ExternalAuthentication module refuses to accept adapter settings JSON that populates fields declared as secrets in the adapter descriptor. Secret values must be supplied through SecretBindings (resolved via a resolver such as IConfiguration), never embedded in the plain AdapterSettings document, to keep secrets out of stored configuration. ThrowIfContainsDeclaredSecret is a guard that fails fast when a configuration connection's settings contain such a field.","triggerScenarios":"Occurs when saving or validating a connection whose AdapterSettings JSON object contains a property matching a secret field name declared by the adapter's ExternalAuthenticationAdapterDescriptor (GetSecretFieldNames), e.g. writing a connection where the client-secret key is placed directly in the settings payload instead of SecretBindings.","commonSituations":"Operators paste a full provider config (including the client secret) into the settings JSON exported from another tool; migrations or older connection formats stored the secret inline before SecretBindings existed; developers guess the settings schema and include secret keys.","solutions":["Remove the secret field from the connection's AdapterSettings JSON.","Add a SecretBindings entry for that field name referencing a resolver (e.g. a configuration path) instead.","Store the actual secret value in the configuration provider (e.g. appsettings/IConfiguration) at the referenced path.","If loading legacy data, migrate old inline secrets into SecretBindings before re-saving."],"exampleFix":"// before\n\"adapterSettings\": { \"authority\": \"https://idp\", \"clientId\": \"app\", \"clientSecret\": \"s3cr3t\" }\n// after\n\"adapterSettings\": { \"authority\": \"https://idp\", \"clientId\": \"app\" },\n\"secretBindings\": { \"clientSecret\": { \"resolverType\": \"Configuration\", \"reference\": \"ExternalAuth:MyIdp:ClientSecret\" } }","handlingStrategy":"validation","validationCode":"// Before saving a connection\nvar names = AdapterSettingsSecretFieldGuard.GetSecretFieldNames(descriptor); // or RedactDeclaredSecrets as a probe\nforeach (var name in names)\n    if (settings.TryGetProperty(name, out _))\n        throw new ArgumentException($\"Move secret field '{name}' from AdapterSettings to SecretBindings.\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat AdapterSettings as non-secret metadata only.","Always put secrets in SecretBindings with a resolver reference.","Run AdapterSettingsSecretFieldGuard.RedactDeclaredSecrets when echoing settings back to clients.","Migrate legacy inline secrets once, at data-import time."],"tags":["configuration","secrets","validation","oidc"],"backgroundTag":"invalid-config-value","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}