diff -ru dnsdist-2.0.2.orig/dnsdist-configuration.hh dnsdist-2.0.2/dnsdist-configuration.hh --- dnsdist-2.0.2.orig/dnsdist-configuration.hh 2025-11-27 14:25:28.000000000 +0100 +++ dnsdist-2.0.2/dnsdist-configuration.hh 2026-02-23 15:01:37.069368518 +0100 @@ -170,6 +170,7 @@ bool d_logConsoleConnections{true}; bool d_addEDNSToSelfGeneratedResponses{true}; bool d_applyACLToProxiedClients{false}; + bool d_webServerAllowCrossOriginRequests{false}; // Whether the webserver / API allows cross-origin requests }; /* Be careful not to hold on this for too long, it can be invalidated diff -ru dnsdist-2.0.2.orig/dnsdist-lua.cc dnsdist-2.0.2/dnsdist-lua.cc --- dnsdist-2.0.2.orig/dnsdist-lua.cc 2025-11-27 14:25:28.000000000 +0100 +++ dnsdist-2.0.2/dnsdist-lua.cc 2026-02-23 15:01:37.070270769 +0100 @@ -1106,6 +1106,7 @@ bool apiRequiresAuthentication{true}; bool dashboardRequiresAuthentication{true}; bool hashPlaintextCredentials = false; + bool allowCrossOriginRequests = false; getOptionalValue(vars, "hashPlaintextCredentials", hashPlaintextCredentials); if (getOptionalValue(vars, "password", password) > 0) { @@ -1145,6 +1146,10 @@ if (getOptionalValue(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) { config.d_dashboardRequiresAuthentication = dashboardRequiresAuthentication; } + + if (getOptionalValue(vars, "allowCrossOriginRequests", allowCrossOriginRequests) > 0) { + config.d_webServerAllowCrossOriginRequests = allowCrossOriginRequests; + } }); int maxConcurrentConnections = 0; diff -ru dnsdist-2.0.2.orig/dnsdist-settings-definitions.yml dnsdist-2.0.2/dnsdist-settings-definitions.yml --- dnsdist-2.0.2.orig/dnsdist-settings-definitions.yml 2025-11-27 14:25:28.000000000 +0100 +++ dnsdist-2.0.2/dnsdist-settings-definitions.yml 2026-02-23 15:01:37.071684427 +0100 @@ -427,6 +427,10 @@ type: "Vec" default: "127.0.0.1, ::1" description: "List of network masks or IP addresses that are allowed to open a connection to the web server" + - name: "allow_cross_origin_requests" + type: "bool" + default: "false" + description: "Whether the webserver allows cross-origin HTTP requests. This might allow a malicious website to read metrics provided by the API if a user's browser has valid credentials cached for the webserver while the user visits the malicious website" - name: "api_requires_authentication" type: "bool" default: "true" diff -ru dnsdist-2.0.2.orig/dnsdist-web.cc dnsdist-2.0.2/dnsdist-web.cc --- dnsdist-2.0.2.orig/dnsdist-web.cc 2025-11-27 14:25:28.000000000 +0100 +++ dnsdist-2.0.2/dnsdist-web.cc 2026-02-23 15:01:37.072518008 +0100 @@ -379,9 +379,10 @@ { const auto origin = req.headers.find("Origin"); if (origin != req.headers.end()) { + const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration(); if (req.method == "OPTIONS") { /* Pre-flight request */ - if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_apiReadWrite) { + if (config.d_apiReadWrite) { resp.headers["Access-Control-Allow-Methods"] = "GET, PUT"; } else { @@ -390,10 +391,13 @@ resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key"; } - resp.headers["Access-Control-Allow-Origin"] = origin->second; + if (config.d_webServerAllowCrossOriginRequests) { + resp.headers["Access-Control-Allow-Origin"] = origin->second; + resp.headers["Vary"] = "Origin"; // prevents cached data to be used for a different Origin - if (isAStatsRequest(req) || isAnAPIRequestAllowedWithWebAuth(req)) { - resp.headers["Access-Control-Allow-Credentials"] = "true"; + if (isAStatsRequest(req) || isAnAPIRequestAllowedWithWebAuth(req)) { + resp.headers["Access-Control-Allow-Credentials"] = "true"; + } } } } diff -ru dnsdist-2.0.2.orig/docs/reference/config.rst dnsdist-2.0.2/docs/reference/config.rst --- dnsdist-2.0.2.orig/docs/reference/config.rst 2025-11-27 14:25:28.000000000 +0100 +++ dnsdist-2.0.2/docs/reference/config.rst 2026-02-23 15:01:37.073639579 +0100 @@ -477,6 +477,7 @@ * ``apiKey=newKey``: string - Changes the API Key (set to an empty string do disable it). Since 1.7.0 the key should be hashed and salted via the :func:`hashPassword` command. * ``customHeaders={[str]=str,...}``: map of string - Allows setting custom headers and removing the defaults. * ``acl=newACL``: string - List of IP addresses, as a string, that are allowed to open a connection to the web server. Defaults to "127.0.0.1, ::1". + * ``allowCrossOriginRequests``: bool - Whether the webserver allows cross-origin HTTP requests. This might allow a malicious website to read metrics provided by the API if a user's browser has valid credentials cached for the webserver while the user visits the malicious website. Default to false. * ``apiRequiresAuthentication``: bool - Whether access to the API (/api endpoints) require a valid API key. Defaults to true. * ``dashboardRequiresAuthentication``: bool - Whether access to the internal dashboard requires a valid password. Defaults to true. * ``statsRequireAuthentication``: bool - Whether access to the statistics (/metrics and /jsonstat endpoints) require a valid password or API key. Defaults to true.