Where: internal/imgcache/urlparser.go, parseImageComponents, on next at 22bfc43. Found by the review of #285.
What is wrong: a # written as %23 in the source path or query of an image URL reaches the parser decoded, and is kept in the source path or query. The source URL built from it holds a #, which Go reads as the start of a fragment and drops before fetching. So /v1/image/allowed.example.com/images/photo.jpg%23x/50x50.jpeg fetches the same upstream image as /v1/image/allowed.example.com/images/photo.jpg/50x50.jpeg, without a signature, but is cached under its own keys. Each new text after the # is another spelling of one upstream request, the problem #275 closes for the host segment.
How it fails: with a host allowlisted, an outsider varies the text after %23 to make pixa fetch and store the same upstream response under as many cache keys as it likes.
Severity: low
Definition of done:
An image URL whose source path or query holds a # (written as %23) is refused with 400; README.md's rule for writing a source path and query says so.
Test first, through the router, for %23 in the path and in the query; make check green.
Package: internal/imgcache. Starts after #285 lands, which changes the same parser.
Model: opus-5-5
Where: `internal/imgcache/urlparser.go`, `parseImageComponents`, on `next` at `22bfc43`. Found by the review of https://git.eeqj.de/sneak/pixa/pulls/285.
What is wrong: a `#` written as `%23` in the source path or query of an image URL reaches the parser decoded, and is kept in the source path or query. The source URL built from it holds a `#`, which Go reads as the start of a fragment and drops before fetching. So `/v1/image/allowed.example.com/images/photo.jpg%23x/50x50.jpeg` fetches the same upstream image as `/v1/image/allowed.example.com/images/photo.jpg/50x50.jpeg`, without a signature, but is cached under its own keys. Each new text after the `#` is another spelling of one upstream request, the problem https://git.eeqj.de/sneak/pixa/issues/275 closes for the host segment.
How it fails: with a host allowlisted, an outsider varies the text after `%23` to make pixa fetch and store the same upstream response under as many cache keys as it likes.
Severity: low
Definition of done:
1. An image URL whose source path or query holds a `#` (written as `%23`) is refused with 400; `README.md`'s rule for writing a source path and query says so.
2. Test first, through the router, for `%23` in the path and in the query; `make check` green.
Package: `internal/imgcache`. Starts after https://git.eeqj.de/sneak/pixa/pulls/285 lands, which changes the same parser.
Model: opus-5-5
clawbot
self-assigned this 2026-10-16 00:47:44 +02:00
Plan: parseImageComponents in internal/imgcache/urlparser.go refuses an image URL with 400 when its source path or query, as decoded, holds a #. The check sits after the host check, so a %23 in the host is still refused as an invalid source host, as #275 made it. The rule in README.md for writing a source path and query (added for #255) gains one sentence: neither may hold a #, and a URL that writes one as %23 is refused with 400. %2523 still passes, as a %23 the source URL keeps.
The tests come first, through the image route: %23 in the source path, %23 in the source query, and %23 in the source path of a URL signed for that exact path, each refused with 400. No existing test changes.
Model: opus-5-5
Plan: `parseImageComponents` in `internal/imgcache/urlparser.go` refuses an image URL with 400 when its source path or query, as decoded, holds a `#`. The check sits after the host check, so a `%23` in the host is still refused as an invalid source host, as https://git.eeqj.de/sneak/pixa/issues/275 made it. The rule in `README.md` for writing a source path and query (added for https://git.eeqj.de/sneak/pixa/issues/255) gains one sentence: neither may hold a `#`, and a URL that writes one as `%23` is refused with 400. `%2523` still passes, as a `%23` the source URL keeps.
The tests come first, through the image route: `%23` in the source path, `%23` in the source query, and `%23` in the source path of a URL signed for that exact path, each refused with 400. No existing test changes.
Model: opus-5-5
Built as planned in #287. The 400 says invalid image URL: invalid image path: the source path or query holds a "#".
Model: opus-5-5
Built as planned in https://git.eeqj.de/sneak/pixa/pulls/287. The 400 says `invalid image URL: invalid image path: the source path or query holds a "#"`.
Model: opus-5-5
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.go,parseImageComponents, onnextat22bfc43. Found by the review of #285.What is wrong: a
#written as%23in the source path or query of an image URL reaches the parser decoded, and is kept in the source path or query. The source URL built from it holds a#, which Go reads as the start of a fragment and drops before fetching. So/v1/image/allowed.example.com/images/photo.jpg%23x/50x50.jpegfetches the same upstream image as/v1/image/allowed.example.com/images/photo.jpg/50x50.jpeg, without a signature, but is cached under its own keys. Each new text after the#is another spelling of one upstream request, the problem #275 closes for the host segment.How it fails: with a host allowlisted, an outsider varies the text after
%23to make pixa fetch and store the same upstream response under as many cache keys as it likes.Severity: low
Definition of done:
#(written as%23) is refused with 400;README.md's rule for writing a source path and query says so.%23in the path and in the query;make checkgreen.Package:
internal/imgcache. Starts after #285 lands, which changes the same parser.Model: opus-5-5
Plan:
parseImageComponentsininternal/imgcache/urlparser.gorefuses an image URL with 400 when its source path or query, as decoded, holds a#. The check sits after the host check, so a%23in the host is still refused as an invalid source host, as #275 made it. The rule inREADME.mdfor writing a source path and query (added for #255) gains one sentence: neither may hold a#, and a URL that writes one as%23is refused with 400.%2523still passes, as a%23the source URL keeps.The tests come first, through the image route:
%23in the source path,%23in the source query, and%23in the source path of a URL signed for that exact path, each refused with 400. No existing test changes.Model: opus-5-5
Built as planned in #287. The 400 says
invalid image URL: invalid image path: the source path or query holds a "#".Model: opus-5-5