{"record":{"id":"1833fa0cf8f4863e","repo":"TechnitiumSoftware/DnsServer","slug":"the-parameter-key-is-an-invalid-settings-param","errorCode":null,"errorMessage":"The '{parameter.Key}' is an invalid Settings parameter.","messagePattern":"The '(.+?)' is an invalid Settings parameter\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"DnsServerCore.HttpApi/HttpApiClient.cs","lineNumber":303,"sourceCode":"\n            return stats;\n        }\n\n        public async Task SetClusterSettingsAsync(string actingUsername, IReadOnlyDictionary<string, string> clusterParameters, CancellationToken cancellationToken = default)\n        {\n            if (!_loggedIn)\n                throw new HttpApiClientException(\"No active session exists. Please login and try again.\");\n\n            if (clusterParameters.Count == 0)\n                throw new ArgumentException(\"At least one parameter must be provided.\", nameof(clusterParameters));\n\n            foreach (KeyValuePair<string, string> parameter in clusterParameters)\n            {\n                switch (parameter.Key)\n                {\n                    case \"token\":\n                    case \"node\":\n                        throw new ArgumentException($\"The '{parameter.Key}' is an invalid Settings parameter.\", nameof(clusterParameters));\n                }\n            }\n\n            HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Post, new Uri(_serverUrl, $\"api/settings/set?actingUser={Uri.EscapeDataString(actingUsername)}\"));\n            httpRequest.Content = new FormUrlEncodedContent(clusterParameters);\n\n            HttpResponseMessage httpResponse = await _httpClient.SendAsync(httpRequest, cancellationToken);\n\n            httpResponse.EnsureSuccessStatusCode();\n\n            using JsonDocument jsonDoc = await JsonDocument.ParseAsync(httpResponse.Content.ReadAsStream(cancellationToken), cancellationToken: cancellationToken);\n            JsonElement rootElement = jsonDoc.RootElement;\n\n            CheckResponseStatus(rootElement);\n        }\n\n        public async Task ForceUpdateBlockListsAsync(string actingUsername, CancellationToken cancellationToken = default)\n        {","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore.HttpApi/HttpApiClient.cs#L285-L321","documentation":"Thrown by SetClusterSettingsAsync when the clusterParameters dictionary contains the reserved key \"token\" or \"node\". These keys are system-managed (authentication token and cluster node identity) and must never be set through the generic api/settings/set endpoint. The client validates input before sending the HTTP request, throwing ArgumentException to fail fast.","triggerScenarios":"Calling SetClusterSettingsAsync with a dictionary whose keys include \"token\" or \"node\", e.g. new Dictionary<string,string>{ {\"token\", \"...\"} } or {\"node\", \"...\"}.","commonSituations":"Round-tripping a full settings snapshot back to the server without filtering internal keys; programmatically iterating all server settings and feeding them wholesale into SetClusterSettingsAsync; typos or copy-paste from a raw config dump that includes auth fields.","solutions":["Remove the \"token\" and \"node\" keys from the dictionary before calling SetClusterSettingsAsync.","Filter the dictionary with a LINQ Where clause that excludes reserved keys.","Build the dictionary from only user-configurable setting names sourced from the API's own settings schema."],"exampleFix":"// before\nvar parameters = new Dictionary<string, string>\n{\n    { \"dnsServerPort\", \"53\" },\n    { \"token\", myToken },   // reserved key — causes ArgumentException\n    { \"node\", \"primary\" }   // reserved key — causes ArgumentException\n};\nawait client.SetClusterSettingsAsync(username, parameters, ct);\n\n// after\nvar reserved = new HashSet<string> { \"token\", \"node\" };\nvar parameters = allSettings\n    .Where(kv => !reserved.Contains(kv.Key))\n    .ToDictionary(kv => kv.Key, kv => kv.Value);\nawait client.SetClusterSettingsAsync(username, parameters, ct);","handlingStrategy":"validation","validationCode":"// Filter out reserved keys before calling SetClusterSettingsAsync\nstatic readonly HashSet<string> ReservedSettingsKeys = new() { \"token\", \"node\" };\n\nDictionary<string, string> SanitizeSettings(IReadOnlyDictionary<string, string> raw)\n{\n    var violations = raw.Keys.Where(k => ReservedSettingsKeys.Contains(k)).ToList();\n    if (violations.Count > 0)\n        throw new ArgumentException($\"Reserved settings keys not allowed: {string.Join(\", \", violations)}\");\n    return raw.ToDictionary(kv => kv.Key, kv => kv.Value);\n}\n\n// usage\nvar safe = SanitizeSettings(rawSettings);\nif (safe.Count > 0)\n    await client.SetClusterSettingsAsync(username, safe, ct);","typeGuard":"// Predicate to check if a settings dictionary is safe to pass\nstatic bool IsValidSettingsDictionary(IReadOnlyDictionary<string, string> parameters)\n{\n    return parameters.Count > 0\n        && !parameters.ContainsKey(\"token\")\n        && !parameters.ContainsKey(\"node\");\n}","tryCatchPattern":"try\n{\n    await client.SetClusterSettingsAsync(username, parameters, ct);\n}\ncatch (ArgumentException ex) when (ex.ParamName == \"clusterParameters\")\n{\n    // a reserved key (\"token\" or \"node\") was present\n    logger.LogWarning(\"Rejected settings update: {Message}\", ex.Message);\n}","preventionTips":["Maintain a constant set of reserved keys (\"token\", \"node\") and filter them whenever building a settings dictionary.","Never round-trip a full raw settings dump — build the update payload from a known-safe schema.","Unit-test the dictionary construction with both reserved and valid keys to confirm filtering."],"tags":["validation","argument","settings","dnsserver","reserved-keys"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}