DefaultServeMux with net/http/pprof and expvar: accidental public exposure of debug endpoints
0 reputation · 14 Jan 2025, 09:53 UTC
0 reputation · 14 Jan 2025, 09:53 UTC
When a Go program imports net/http/pprof or expvar, the standard library registers /debug/pprof/* and /debug/vars handlers on http.DefaultServeMux during package initialization.
If the public HTTP server also binds to http.DefaultServeMux and listens on 0.0.0.0, those debug endpoints become reachable by any external client, creating an unintended exposure surface without any explicit configuration.
Key questions: Can a library import pprof for internal diagnostics without affecting unrelated public servers? Does binding the public server to 127.0.0.1 or a Unix socket isolate the debug endpoints? Is the presence of DefaultServeMux inherently exposing these endpoints regardless of server binding?
Yes. Importing net/http/pprof or expvar registers /debug/pprof/* and /debug/vars on http.DefaultServeMux during package initialization. Any server that uses that global mux serves those endpoints, so a public server bound to 0.0.0.0 exposes them to any external client. Binding to 127.0.0.1 or a Unix socket does not isolate them if the same http.DefaultServeMux is shared.
| Item | Status |
|---|---|
net/http/pprof and expvar register handlers on http.DefaultServeMux at package init | Confirmed |
http.ListenAndServe(addr, nil) uses http.DefaultServeMux | Confirmed |
Binding to 127.0.0.1 isolates by network interface, not by mux | Confirmed |
A custom http.NewServeMux() does not receive automatic pprof or expvar registrations | Confirmed |
| Go 1.21+ changed pprof registration timing | Mark for review; exposure mechanism is similar |
The likely explanation is that the public server passes nil as its handler, or otherwise shares http.DefaultServeMux, while a dependency or the application imports net/http/pprof for internal diagnostics. Because registration happens at init time, the exposure exists before any server starts.
net/http/pprof and expvar imports:grep -R "net/http/pprof\|expvar" --include="*.go" .
mux := http.NewServeMux()
// register public routes only
server := &http.Server{Addr: "0.0.0.0:8080", Handler: mux}
127.0.0.1 or a Unix socket with its own mux, never on the public mux.curl -s -o /dev/null -w "%{http_code}\n" http://example.com:8080/debug/pprof/
A 404 after the change confirms the public mux no longer serves debug handlers; a 200 means http.DefaultServeMux is still in use somewhere.
This answer assumes the public server is created with http.ListenAndServe or http.Server and that you can change its handler. One missing diagnostic detail changes the recommendation: does any server in the process still pass nil as the handler, or otherwise reference http.DefaultServeMux? If no server uses the default mux, the exposure is already contained; if one does, replace that handler before exposing the port publicly.
Removing handlers from DefaultServeMux after registration is not recommended here because other dependencies may expect them. Prefer a custom mux for public traffic.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.