@marcel91200 / caddy Mirror · read-only / commits / 612128e3
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 02:15 UTC

logging: Parse request URIs in the `query` filter like the server does (#8255)

Jay Hemnani committed Oct 19, 2026 at 13:13 UTC 612128e3b0f290af44649d7a7dcb1111fc3ebb33
2 files changed +26 -1
modules/logging/filters.go
+9 -1
@@ -415,7 +415,15 @@ func (m QueryFilter) Filter(in zapcore.Field) zapcore.Field {
415 }
416
417 func (m QueryFilter) processQueryString(s string) string {
418 - u, err := url.Parse(s)
418 + parse := url.Parse
419 + if strings.HasPrefix(s, "/") {
420 + // parse a request-target like the server does. Browsers and most
421 + // clients strip a #fragment before sending, but a client that
422 + // sends a raw '#' has it kept in the query by the server
423 + // (url.ParseRequestURI does not split it off), so filter it too
424 + parse = url.ParseRequestURI
425 + }
426 + u, err := parse(s)
427 if err != nil {
428 return s
429 }
modules/logging/filters_test.go
+17
@@ -127,6 +127,23 @@ func TestQueryFilterSingleValue(t *testing.T) {
127 }
128 }
129
130 +func TestQueryFilterHashInRequestURI(t *testing.T) {
131 + f := QueryFilter{[]queryFilterAction{
132 + {replaceAction, "token", "REDACTED"},
133 + }}
134 +
135 + // A request-target has no fragment, so '#' is part of the query.
136 + for uri, expected := range map[string]string{
137 + "/path?x=1#&token=secret": "/path?token=REDACTED&x=1%23",
138 + "/path?token=secret#%zz": "/path",
139 + } {
140 + out := f.Filter(zapcore.Field{String: uri})
141 + if out.String != expected {
142 + t.Errorf("filtering %q: got %q, expected %q", uri, out.String, expected)
143 + }
144 + }
145 +}
146 +
147 func TestQueryFilterMultiValue(t *testing.T) {
148 f := QueryFilter{
149 Actions: []queryFilterAction{