Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#8001·caddy

strip_path_prefix and handle_path silently fail when the prefix contains non-ASCII characters

Author: youdie006Created Sep 7, 2026Updated Sep 7, 2026

Issue Details

handle_path, strip_path_prefix and strip_path_suffix leave the path untouched whenever the prefix or suffix contains a non-ASCII character. The path matcher generated by the same handle_path token agrees the prefix is present, so the route runs and the upstream receives the un-stripped path.

Measured on master (e05f57dc) by driving the two handlers that one handle_path /<dir>/* token produces (modules/caddyhttp/rewrite/caddyfile.go:275-287) - caddyhttp.MatchPath.MatchWithError and rewrite.Rewrite.Rewrite:

ascii         matcher says prefix present=true   strip produced "/x"
escaped-utf8  matcher says prefix present=true   strip produced "/café/x"
literal-utf8  matcher says prefix present=true   strip produced "/café/x"
korean        matcher says prefix present=true   strip produced "/한/x"

Request paths were /api/x, /caf%C3%A9/x, /café/x and /%ED%95%9C/x; the expected result is /x in every row. So handle_path /café/* forwards /café/x to the upstream instead of /x.

Root cause

modules/caddyhttp/rewrite/rewrite.go:436:

ch := string(escapedPath[iPath])

escapedPath[iPath] is a byte, so this is an integer-to-string conversion: byte 0xC3 becomes the two-byte string "Ã". The percent-decoding branch just below instead assigns ch from url.PathUnescape("%C3"), which is the one-byte string "\xc3". The strings.EqualFold at :450 therefore compares one byte against two, returns false, and the function returns the path untrimmed.

URL.EscapedPath() escapes every multi-byte character, so /café/x and /caf%C3%A9/x both arrive as /caf%C3%A9/x - which is why the unescaped form fails too. Every non-ASCII prefix hits this.

trimPathSuffix has the same conversion at rewrite.go:483 and :504.

One thing worth knowing before fixing it

Comparing the raw bytes directly with strings.EqualFold is not safe. A single byte above 0x7F is invalid UTF-8 and decodes to RuneError, so any two distinct high bytes fold equal:

strings.EqualFold("\xc3", "\xc4") = true
strings.EqualFold("\xed", "\xf0") = true

That would make strip_path_prefix /한 strip /漢. Today's behaviour at least fails closed - the comparison never matches, so nothing is stripped. A fix needs a byte comparison with ASCII-only case folding, which also keeps the case-insensitivity the comment at :448 says is intentional.

I have a patch along those lines with tests and am happy to open a PR if you would like one.

Assistance Disclosure

AI used

If AI was used, describe the extent to which it was used.

Claude Code located the divergence, wrote the reproductions quoted above, and drafted the candidate patch. The output blocks are verbatim from running those probes against master.

Source: caddyserver/caddy

View original on GitHubView discussion on GitHub