Files
caddy/modules/caddyhttp/server_error_headers_test.go
Matthew HoltandCopilot 09630fa5fa caddyhttp: sanitize header aliases before request validation
Apply the existing header-alias rules before method and host validation, so early errors cannot carry spoofed identity aliases into user-defined error routes. Verify all early error paths with synthetic authentication headers.

Addresses scan report 3ebe3dfa-8541-44a7-8f1f-cd5071d55a67. The supplied checker has a malformed Authorization header; regression tests use independently constructed requests.

Code written by GitHub Copilot (GPT-6.1 Sol, Claude Opus 5.5); reviewed by mholt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-11 11:06:41 -07:00

64 lines
2.0 KiB
Go

package caddyhttp
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"go.uber.org/zap"
)
func TestServerErrorRoutesReceiveSanitizedHeaders(t *testing.T) {
for _, invalid := range []string{"method", "missing host", "invalid host"} {
t.Run(invalid, func(t *testing.T) {
reachedErrorRoute := false
server := &Server{
logger: zap.NewNop(), errorLogger: zap.NewNop(), accessLogger: zap.NewNop(),
Errors: &HTTPErrorConfig{Routes: RouteList{{}}},
primaryHandlerChain: HandlerFunc(func(http.ResponseWriter, *http.Request) error {
t.Fatal("invalid request reached primary routes")
return nil
}),
errorHandlerChain: HandlerFunc(func(w http.ResponseWriter, req *http.Request) error {
reachedErrorRoute = true
if req.Header.Get("Authorization") != "synthetic" {
t.Fatal("normal authorization header was removed")
}
// Stand in for a trusted authentication response copied into the request.
req.Header.Set("Remote-User", "alice")
var users []string
for name, values := range req.Header {
if strings.NewReplacer("-", "_", ".", "_").Replace(strings.ToUpper(name)) == "REMOTE_USER" {
users = append(users, values...)
}
}
if len(users) != 1 || users[0] != "alice" {
t.Fatalf("backend identity collision: %v", users)
}
w.WriteHeader(http.StatusForbidden)
return nil
}),
}
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("Authorization", "synthetic")
req.Header.Set("Remote_user", "admin")
req.Header.Set("Remote.user", "admin")
req.Header.Set("Remote_.user", "admin")
switch invalid {
case "method":
req.Method = strings.Repeat("A", 33)
case "missing host":
req.Host = ""
case "invalid host":
req.Host = "..."
}
rec := httptest.NewRecorder()
server.ServeHTTP(rec, req)
if !reachedErrorRoute || rec.Code != http.StatusForbidden {
t.Fatalf("error route not reached: status %d", rec.Code)
}
})
}
}