Where: internal/imgcache/urlparser.goparseImageComponents on next at c23585d: the host segment is everything before the first / of the wildcard, and only @ is refused in it; the source query is split from the path only. Found by the review of #266.
What is wrong: a ? written into the host segment as %3F reaches the parser decoded, so /v1/image/cdn.example.com%3Fx=1/a.jpg/100x100.jpg gives the host cdn.example.com?x=1. The source URL built from it is https://cdn.example.com?x=1/a.jpg, which Go reads as host cdn.example.com with the query x=1/a.jpg: the allowlist passes it, pixa fetches the host's root path with that query, and the result is cached under the host as written. Each new text after the ? is another unsigned spelling of one upstream request with its own cache keys, the problem #246 closed for letter case and ports.
How it fails: with a host allowlisted, an outsider varies the text after %3F to make pixa fetch and store the same upstream response under as many cache keys as it likes.
Severity: low
Definition of done:
parseImageComponents refuses with 400 a host segment holding anything a URL host cannot hold (at least ?, #, %, \ and whitespace, besides @); a host name, an IPv4 address, a bracketed IPv6 address and a : with a port stay accepted, as #246 left them.
Test first for %3F and # in the host segment; make check green.
Package: internal/imgcache.
Model: opus-5-5
Where: `internal/imgcache/urlparser.go` `parseImageComponents` on `next` at `c23585d`: the host segment is everything before the first `/` of the wildcard, and only `@` is refused in it; the source query is split from the path only. Found by the review of https://git.eeqj.de/sneak/pixa/pulls/266.
What is wrong: a `?` written into the host segment as `%3F` reaches the parser decoded, so `/v1/image/cdn.example.com%3Fx=1/a.jpg/100x100.jpg` gives the host `cdn.example.com?x=1`. The source URL built from it is `https://cdn.example.com?x=1/a.jpg`, which Go reads as host `cdn.example.com` with the query `x=1/a.jpg`: the allowlist passes it, pixa fetches the host's root path with that query, and the result is cached under the host as written. Each new text after the `?` is another unsigned spelling of one upstream request with its own cache keys, the problem https://git.eeqj.de/sneak/pixa/issues/246 closed for letter case and ports.
How it fails: with a host allowlisted, an outsider varies the text after `%3F` to make pixa fetch and store the same upstream response under as many cache keys as it likes.
Severity: low
Definition of done:
1. `parseImageComponents` refuses with 400 a host segment holding anything a URL host cannot hold (at least `?`, `#`, `%`, `\` and whitespace, besides `@`); a host name, an IPv4 address, a bracketed IPv6 address and a `:` with a port stay accepted, as https://git.eeqj.de/sneak/pixa/issues/246 left them.
2. Test first for `%3F` and `#` in the host segment; `make check` green.
Package: `internal/imgcache`.
Model: opus-5-5
clawbot
self-assigned this 2026-10-15 12:31:47 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Where:
internal/imgcache/urlparser.goparseImageComponentsonnextatc23585d: the host segment is everything before the first/of the wildcard, and only@is refused in it; the source query is split from the path only. Found by the review of #266.What is wrong: a
?written into the host segment as%3Freaches the parser decoded, so/v1/image/cdn.example.com%3Fx=1/a.jpg/100x100.jpggives the hostcdn.example.com?x=1. The source URL built from it ishttps://cdn.example.com?x=1/a.jpg, which Go reads as hostcdn.example.comwith the queryx=1/a.jpg: the allowlist passes it, pixa fetches the host's root path with that query, and the result is cached under the host as written. Each new text after the?is another unsigned spelling of one upstream request with its own cache keys, the problem #246 closed for letter case and ports.How it fails: with a host allowlisted, an outsider varies the text after
%3Fto make pixa fetch and store the same upstream response under as many cache keys as it likes.Severity: low
Definition of done:
parseImageComponentsrefuses with 400 a host segment holding anything a URL host cannot hold (at least?,#,%,\and whitespace, besides@); a host name, an IPv4 address, a bracketed IPv6 address and a:with a port stay accepted, as #246 left them.%3Fand#in the host segment;make checkgreen.Package:
internal/imgcache.Model: opus-5-5