docs(passthrough): drop out-of-scope "." rejection, correct %25 comment

Two doc/scope corrections from the round-3, 2026-08-22 review (FOLDED +
SHOULD-5), no behavior change to the traversal bypasses finding 4 itself
fixed:

- hasTraversalSegment rejected an exact "." segment alongside "..",
  outside finding 4's actual scope: "v1/x/./y" is a legal, non-traversal
  path and was silently getting 400'd. Dropped the "." check; only ".."
  is rejected again, matching every other traversal rule this function
  enforces.

- The double-encoding rejection's own comment claimed it "rejects an
  intentionally double-encoded %25" -- overstated. "%25" is the correct,
  single, standards-conforming encoding of a literal "%" (RFC 3986);
  after exactly one unescape pass, a legitimate single-encoded literal
  "%" is byte-for-byte indistinguishable from a genuine double-encoding.
  The behavior (fail closed) is still correct and unchanged -- refusing
  an occasional literal "%" is a far smaller cost than admitting a real
  double-encoded traversal -- but the comment now says so honestly,
  including the concrete false-positive example ("v1/100%25done" decodes
  to "v1/100%done" and is rejected even though it names a real,
  non-malicious resource).

Tests: added "v1/x/./y" (must not 400) and "v1/100%25done" (the
documented false positive, still 403s) to hasTraversalSegment's table.
This commit is contained in:
2026-08-22 14:50:00 +01:00
parent c69210fd91
commit 64126db7d4
2 changed files with 37 additions and 10 deletions
+12 -2
View File
@@ -225,11 +225,21 @@ func TestHasTraversalSegment(t *testing.T) {
// a second time).
{"%252e%252e/x", true},
{"%252E%252E/x", true},
{"a%25b", true}, // a literal, intentionally double-encoded "%25" (a percent sign) is refused too — accepted trade-off, no legitimate use in this package
// A false positive, KNOWN and accepted (SHOULD-5, round 3
// review, corrected doc comment): "%25" is the correct, single
// encoding of a literal "%", indistinguishable after one decode
// pass from a genuine double-encoding — this rejects a
// completely benign request naming a literal "%" in its path.
{"a%25b", true},
{"v1/100%25done", true}, // decodes once to "v1/100%done" — a real resource path, still rejected
// Existing correct behavior must be unchanged: a legitimate
// single-encoded "/" within one segment, and literal "..".
// single-encoded "/" within one segment, literal "..", and a
// literal "." segment (dropped from finding 4's scope, round 3
// review — "v1/x/./y" is a legal, non-traversal path and must
// not 400).
{"a%2Fb%2Fc", false},
{"v1/x/./y", false},
}
for _, tt := range tests {
t.Run(tt.rest, func(t *testing.T) {