diff --git a/recovery.go b/recovery.go index 23492464..128654ac 100644 --- a/recovery.go +++ b/recovery.go @@ -100,22 +100,21 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { func secureRequestDump(r *http.Request) string { httpRequest, _ := httputil.DumpRequest(r, false) lines := strings.Split(bytesconv.BytesToString(httpRequest), "\r\n") + const ( + authPrefix = "Authorization:" + proxyPrefix = "Proxy-Authorization:" + ) for i, line := range lines { switch { - case hasHeaderPrefixFold(line, "Authorization:"): + case len(line) >= len(authPrefix) && strings.EqualFold(line[:len(authPrefix)], authPrefix): lines[i] = "Authorization: *" - case hasHeaderPrefixFold(line, "Proxy-Authorization:"): + case len(line) >= len(proxyPrefix) && strings.EqualFold(line[:len(proxyPrefix)], proxyPrefix): lines[i] = "Proxy-Authorization: *" } } return strings.Join(lines, "\r\n") } -// hasHeaderPrefixFold reports whether line begins with prefix, ignoring ASCII case. -func hasHeaderPrefixFold(line, prefix string) bool { - return len(line) >= len(prefix) && strings.EqualFold(line[:len(prefix)], prefix) -} - func defaultHandleRecovery(c *Context, _ any) { c.AbortWithStatus(http.StatusInternalServerError) } diff --git a/recovery_test.go b/recovery_test.go index 8749daba..151f2ccc 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -274,20 +274,32 @@ func TestSecureRequestDump(t *testing.T) { wantNotContain: "Bearer secret-token", }, { + // Bypass http.Header.Set canonicalization to put a lowercase + // header name on the wire and verify case-insensitive matching. name: "authorization header lowercase", req: func() *http.Request { r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) - r.Header.Set("authorization", "some-secret") + r.Header["authorization"] = []string{"some-secret"} return r }(), wantContains: "Authorization: *", wantNotContain: "some-secret", }, + { + name: "AUTHORIZATION header uppercase", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header["AUTHORIZATION"] = []string{"UPPER-SECRET"} + return r + }(), + wantContains: "Authorization: *", + wantNotContain: "UPPER-SECRET", + }, { name: "Authorization header mixed case", req: func() *http.Request { r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) - r.Header.Set("AuThOrIzAtIoN", "token123") + r.Header["AuThOrIzAtIoN"] = []string{"token123"} return r }(), wantContains: "Authorization: *", @@ -303,6 +315,26 @@ func TestSecureRequestDump(t *testing.T) { wantContains: "Proxy-Authorization: *", wantNotContain: "Basic cHJveHk6c2VjcmV0", }, + { + name: "proxy-authorization header lowercase", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header["proxy-authorization"] = []string{"Basic bG93ZXI="} + return r + }(), + wantContains: "Proxy-Authorization: *", + wantNotContain: "Basic bG93ZXI=", + }, + { + name: "PROXY-AUTHORIZATION header uppercase", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header["PROXY-AUTHORIZATION"] = []string{"Basic VVBQRVI="} + return r + }(), + wantContains: "Proxy-Authorization: *", + wantNotContain: "Basic VVBQRVI=", + }, { name: "No Authorization header", req: func() *http.Request {