The original concern is functional: a resource pattern should treat a percent-encoded segment like some%2Fvalue as a single opaque token rather than splitting it into two path segments at the decoded /. Investigation into why %2F was being decoded and how routes matched against the result surfaced three related security issues, documented below.
Rather than landing a fix directly, the problem space warrants discussion first. Different fixes carry different compliance and compatibility tradeoffs, and every viable option is a breaking change in some form. Aligning on a direction before committing to an implementation is the safer path.
Go's net/http decodes percent-encoded characters when it parses an incoming URL:
%2F becomes / in r.URL.Path, while the original encoded form is preserved in
r.URL.RawPath. Two different parts of s3-proxy use different fields:
r.URL.RequestURI(), which returns the encoded
form (from RawPath when available). It sees %2F as literal characters, not
as path separators.r.URL.Path to build the S3 key. It sees the
decoded form, where %2F has already become /.All three issues stem from this mismatch, combined with how glob patterns are compiled. The examples below use PUT for concreteness, but the auth bypass applies to any HTTP method — a config that restricts GET or DELETE on a namespace is equally affected, meaning an attacker could read from or delete objects in a protected namespace without credentials.
RFC 3986 §2.2 states that / and %2F are not equivalent in a URI path:
URIs that differ in the replacement of a reserved character with its corresponding percent-encoded octet are not equivalent.
/ is a reserved gen-delim used as a path segment separator. %2F is its
percent-encoded form and, by the RFC, should be treated as data...
0.0.0-20260424211602-1320e4abd46aExploitability
AV:NAC:LPR:NUI:NScope
S:UImpact
C:HI:HA:L9.4/CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L