Information disclosure
Description
The commit implements a security fix by adding a configuration flag http.header.disableServerHostname to disable the X-Server-Hostname header in HTTP responses. Previously, VictoriaMetrics components always included X-Server-Hostname with the server's hostname in responses, which leaks internal host information. This patch mitigates the information disclosure risk by allowing operators to suppress the header. Tests were added to verify the option's behavior. This is a targeted fix for information disclosure via an HTTP response header.
Proof of Concept
PoC (demonstrates information disclosure prior to the fix):
1) Pre-fix behavior (v1.139.0 or earlier): The HTTP response includes X-Server-Hostname with the server's hostname.
Example using curl:
curl -I http://victoriametrics.example/health
HTTP/1.1 200 OK
Date: ...
X-Server-Hostname: vm-01.internal.example
...
2) Exploitability: Any entity able to access the service can learn the internal hostname of the server from the response headers. This can aid in targeted reconnaissance, social engineering, or internal mapping.
3) Post-fix behavior (with the patch applied): The X-Server-Hostname header can be disabled via the new flag, and will not be present in responses when enabled.
Example (with the fix enabled):
curl -I http://victoriametrics.example/health
HTTP/1.1 200 OK
Date: ...
...
(No X-Server-Hostname header)
4) Optional verification in code:
- Bash (curl): curl -I http://<target>/health | grep -i X-Server-Hostname || true
- Python (requests):
import requests
r = requests.get('http://<target>/health')
print('X-Server-Hostname' in r.headers, r.headers.get('X-Server-Hostname'))
Commit Details
Author: Zasda Yusuf Mikail
Date: 2026-06-15 07:05 UTC
Message:
lib/httpserver: allow disabling server hostname header
When responding to an HTTP request, VictoriaMetrics components include the X-Server-Hostname.
While this may be useful for debugging, it also leaks the hostname.
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11067
Triage Assessment
Vulnerability Type: Information disclosure
Confidence: HIGH
Reasoning:
The commit introduces a configuration to disable the X-Server-Hostname header in HTTP responses, preventing leakage of the server's hostname. This mitigates information disclosure risk. The tests also validate the option to disable the header.
Verification Assessment
Vulnerability Type: Information disclosure
Confidence: HIGH
Affected Versions: 1.139.0 and earlier
Code Diff
diff --git a/lib/httpserver/httpserver.go b/lib/httpserver/httpserver.go
index 19784cb3ae6a5..c0c0d75e79471 100644
--- a/lib/httpserver/httpserver.go
+++ b/lib/httpserver/httpserver.go
@@ -64,9 +64,10 @@ var (
connTimeout = flag.Duration("http.connTimeout", 2*time.Minute, "Incoming connections to -httpListenAddr are closed after the configured timeout. "+
"This may help evenly spreading load among a cluster of services behind TCP-level load balancer. Zero value disables closing of incoming connections")
- headerHSTS = flag.String("http.header.hsts", "", "Value for 'Strict-Transport-Security' header, recommended: 'max-age=31536000; includeSubDomains'")
- headerFrameOptions = flag.String("http.header.frameOptions", "", "Value for 'X-Frame-Options' header")
- headerCSP = flag.String("http.header.csp", "", `Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"`)
+ headerHSTS = flag.String("http.header.hsts", "", "Value for 'Strict-Transport-Security' header, recommended: 'max-age=31536000; includeSubDomains'")
+ headerFrameOptions = flag.String("http.header.frameOptions", "", "Value for 'X-Frame-Options' header")
+ headerCSP = flag.String("http.header.csp", "", `Value for 'Content-Security-Policy' header, recommended: "default-src 'self'"`)
+ headerDisableServerHostname = flag.Bool("http.header.disableServerHostname", false, "Whether to disable 'X-Server-Hostname' header in HTTP responses")
disableCORS = flag.Bool("http.disableCORS", false, `Disable CORS for all origins (*)`)
)
@@ -329,7 +330,9 @@ func handlerWrapper(w http.ResponseWriter, r *http.Request, rh RequestHandler) {
if *headerCSP != "" {
h.Add("Content-Security-Policy", *headerCSP)
}
- h.Add("X-Server-Hostname", hostname)
+ if !*headerDisableServerHostname {
+ h.Add("X-Server-Hostname", hostname)
+ }
requestsTotal.Inc()
if whetherToCloseConn(r) {
connTimeoutClosedConns.Inc()
diff --git a/lib/httpserver/httpserver_test.go b/lib/httpserver/httpserver_test.go
index 474ea44663c15..096778c7c5117 100644
--- a/lib/httpserver/httpserver_test.go
+++ b/lib/httpserver/httpserver_test.go
@@ -228,4 +228,30 @@ func TestHandlerWrapper(t *testing.T) {
if got := h.Get("Content-Security-Policy"); got != cspHeader {
t.Fatalf("unexpected CSP header; got %q; want %q", got, cspHeader)
}
+ if got := h.Get("X-Server-Hostname"); got != hostname {
+ t.Fatalf("unexpected X-Server-Hostname header; got %q; want %q", got, hostname)
+ }
+}
+
+func TestHandlerWrapperDisableServerHostnameHeader(t *testing.T) {
+ origDisableServerHostname := *headerDisableServerHostname
+ *headerDisableServerHostname = true
+ defer func() {
+ *headerDisableServerHostname = origDisableServerHostname
+ }()
+
+ req, _ := http.NewRequest("GET", "/health", nil)
+
+ srv := &server{s: &http.Server{}}
+ w := &httptest.ResponseRecorder{}
+
+ handlerWrapper(w, req, func(w http.ResponseWriter, r *http.Request) bool {
+ return builtinRoutesHandler(srv, r, w, func(_ http.ResponseWriter, _ *http.Request) bool {
+ return true
+ })
+ })
+
+ if got := w.Header().Get("X-Server-Hostname"); got != "" {
+ t.Fatalf("unexpected X-Server-Hostname header; got %q; want empty value", got)
+ }
}