TechnitiumSoftware/DnsServer · error · ArgumentException

The ' ' is an invalid Settings parameter.

Error message

The '{parameter.Key}' is an invalid Settings parameter.

What it means

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.

Solutions

  1. Remove the "token" and "node" keys from the dictionary before calling SetClusterSettingsAsync.
  2. Filter the dictionary with a LINQ Where clause that excludes reserved keys.
  3. Build the dictionary from only user-configurable setting names sourced from the API's own settings schema.

Example fix

// before
var parameters = new Dictionary<string, string>
{
    { "dnsServerPort", "53" },
    { "token", myToken },   // reserved key — causes ArgumentException
    { "node", "primary" }   // reserved key — causes ArgumentException
};
await client.SetClusterSettingsAsync(username, parameters, ct);

// after
var reserved = new HashSet<string> { "token", "node" };
var parameters = allSettings
    .Where(kv => !reserved.Contains(kv.Key))
    .ToDictionary(kv => kv.Key, kv => kv.Value);
await client.SetClusterSettingsAsync(username, parameters, ct);
Defensive patterns

Strategy: validation

Validate before calling

// Filter out reserved keys before calling SetClusterSettingsAsync
static readonly HashSet<string> ReservedSettingsKeys = new() { "token", "node" };

Dictionary<string, string> SanitizeSettings(IReadOnlyDictionary<string, string> raw)
{
    var violations = raw.Keys.Where(k => ReservedSettingsKeys.Contains(k)).ToList();
    if (violations.Count > 0)
        throw new ArgumentException($"Reserved settings keys not allowed: {string.Join(", ", violations)}");
    return raw.ToDictionary(kv => kv.Key, kv => kv.Value);
}

// usage
var safe = SanitizeSettings(rawSettings);
if (safe.Count > 0)
    await client.SetClusterSettingsAsync(username, safe, ct);

Type guard

// Predicate to check if a settings dictionary is safe to pass
static bool IsValidSettingsDictionary(IReadOnlyDictionary<string, string> parameters)
{
    return parameters.Count > 0
        && !parameters.ContainsKey("token")
        && !parameters.ContainsKey("node");
}

Try / catch

try
{
    await client.SetClusterSettingsAsync(username, parameters, ct);
}
catch (ArgumentException ex) when (ex.ParamName == "clusterParameters")
{
    // a reserved key ("token" or "node") was present
    logger.LogWarning("Rejected settings update: {Message}", ex.Message);
}

Prevention

When it happens

Trigger: Calling SetClusterSettingsAsync with a dictionary whose keys include "token" or "node", e.g. new Dictionary<string,string>{ {"token", "..."} } or {"node", "..."}.

Common situations: 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.

Related errors


AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13). Data as JSON: /api/errors/1833fa0cf8f4863e. Report an issue: GitHub.

Appendix: source

Thrown at DnsServerCore.HttpApi/HttpApiClient.cs:303

            return stats;
        }

        public async Task SetClusterSettingsAsync(string actingUsername, IReadOnlyDictionary<string, string> clusterParameters, CancellationToken cancellationToken = default)
        {
            if (!_loggedIn)
                throw new HttpApiClientException("No active session exists. Please login and try again.");

            if (clusterParameters.Count == 0)
                throw new ArgumentException("At least one parameter must be provided.", nameof(clusterParameters));

            foreach (KeyValuePair<string, string> parameter in clusterParameters)
            {
                switch (parameter.Key)
                {
                    case "token":
                    case "node":
                        throw new ArgumentException($"The '{parameter.Key}' is an invalid Settings parameter.", nameof(clusterParameters));
                }
            }

            HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Post, new Uri(_serverUrl, $"api/settings/set?actingUser={Uri.EscapeDataString(actingUsername)}"));
            httpRequest.Content = new FormUrlEncodedContent(clusterParameters);

            HttpResponseMessage httpResponse = await _httpClient.SendAsync(httpRequest, cancellationToken);

            httpResponse.EnsureSuccessStatusCode();

            using JsonDocument jsonDoc = await JsonDocument.ParseAsync(httpResponse.Content.ReadAsStream(cancellationToken), cancellationToken: cancellationToken);
            JsonElement rootElement = jsonDoc.RootElement;

            CheckResponseStatus(rootElement);
        }

        public async Task ForceUpdateBlockListsAsync(string actingUsername, CancellationToken cancellationToken = default)
        {

View on GitHub (pinned to d0484b6c1e)