Files
selfpost/internal/web/healthz_test.go
T
mixandClaude Opus 5.5 a327962008 web: /healthz is answered only to local probes (audit I6)
Security audit 2026-10-17, finding I6 (low), stage 3. /healthz is
registered outside auth and every proxy fragment passed it through: the
live server answered "ok" to the internet, and "unhealthy: postfix"
would have been a free tell.

Now handleHealth answers only a request with no X-Forwarded-For (nor
Forwarded) from loopback or a private address: the image's HEALTHCHECK
(127.0.0.1), the host through the loopback-published port (the Docker
gateway), a monitor on the Docker network. Every supported proxy appends
X-Forwarded-For, so through it the endpoint is 404. The plan named
"trusted-proxy peers"; that would have let the internet in again through
the trusted proxy, hence this shape. The e2e's readiness probe is the
host's, and stays answered.

Test: TestHealthzAnswersOnlyLocalProbes. Docs: guide § Container health,
architecture.md; CHANGELOG; plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:17:27 +03:00

35 lines
1.1 KiB
Go

package web
import (
"net/http"
"net/http/httptest"
"testing"
)
// /healthz answers a probe that did not come through a proxy, from loopback
// or a private address, and is not found for anyone else (security audit
// 2026-10-17, I6). Liveness itself is not under test here: a probe that is
// answered gets 200 or 503, never 404.
func TestHealthzAnswersOnlyLocalProbes(t *testing.T) {
for _, c := range []struct {
name, remote, xff string
answered bool
}{
{"the container's own probe", "127.0.0.1:40000", "", true},
{"the host through the published port", "172.18.0.1:40000", "", true},
{"through the reverse proxy", "172.18.0.1:40000", "198.51.100.7", false},
{"a public address", "198.51.100.7:40000", "", false},
} {
r := httptest.NewRequest(http.MethodGet, "/healthz", nil)
r.RemoteAddr = c.remote
if c.xff != "" {
r.Header.Set("X-Forwarded-For", c.xff)
}
rec := httptest.NewRecorder()
handleHealth(rec, r)
if got := rec.Code != http.StatusNotFound; got != c.answered {
t.Errorf("%s: %d, answered %v, want %v", c.name, rec.Code, got, c.answered)
}
}
}