Test that a source path or query holding %25 passes the signature check
check / check (push) Canceled after 0s

A source URL holding %25, a literal "%", written by the README rule or by
GenerateSignedURL, is refused with 400: the path traversal check refuses
the "%" that one more decoding leaves. A double-encoded ".." next to such
a "%" must stay refused.

Model: opus-5-5
This commit is contained in:
2026-10-15 21:40:55 +00:00
parent f9c878161a
commit c49d509a21
2 changed files with 135 additions and 0 deletions
@@ -407,6 +407,71 @@ func TestParseImagePath_PathTraversal(t *testing.T) {
}
}
// TestParseImageURL_SourceURLHoldingPercent25 verifies that a source path and
// a source query holding %25, the percent-escape of a literal "%", are
// accepted as they are, although decoding them once more leaves a "%" that
// does not start a percent-escape.
func TestParseImageURL_SourceURLHoldingPercent25(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
want *ParsedURL
}{
{
name: "path",
input: "/v1/image/cdn.example.com/sale/50%25off.jpg/800x600.webp",
want: &ParsedURL{
Host: testHostCDN, Path: "/sale/50%25off.jpg",
Size: Size{Width: 800, Height: 600}, Format: FormatWebP,
},
},
{
name: "query",
input: "/v1/image/cdn.example.com/photos/cat.jpg?discount=50%25/800x600.webp",
want: &ParsedURL{
Host: testHostCDN, Path: testPathCat, Query: "discount=50%25",
Size: Size{Width: 800, Height: 600}, Format: FormatWebP,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := ParseImageURL(tt.input)
if err != nil {
t.Fatalf("ParseImageURL() unexpected error = %v", err)
}
assertParsedURL(t, got, tt.want)
})
}
}
// TestParseImageURL_DoubleEncodedTraversalNextToPercent verifies that a
// double-encoded ".." is still refused when the path also holds a "%" that
// does not start a percent-escape, either as written or after one more
// decoding.
func TestParseImageURL_DoubleEncodedTraversalNextToPercent(t *testing.T) {
t.Parallel()
for _, input := range []string{
"/v1/image/cdn.example.com/photos/%252e%252e/secret/50%25off.jpg/800x600.jpeg",
"/v1/image/cdn.example.com/photos/%252e%252e/secret/100%/800x600.jpeg",
} {
_, err := ParseImageURL(input)
t.Logf("ParseImageURL(%q) error = %v", input, err)
if !errors.Is(err, ErrPathTraversal) {
t.Errorf("ParseImageURL(%q) error = %v, want ErrPathTraversal",
input, err)
}
}
}
// errorIs checks if err matches target (handles wrapped errors).
func errorIs(err, target error) bool {
if errors.Is(err, target) {