A # written as %23 in the source path or source query of a /v1/image/ URL reached parseImageComponents decoded and stayed there. The source URL built from it ended at the # when it was fetched, so each text after it fetched the same upstream image (without a signature for an allowlisted host) and cached it under keys of its own. Such a URL is now refused with 400, and the rule in README.md for writing a source path and query says neither may hold a #.
What the diff does not show:
The check runs after the host check, so a %23 in the host is still refused as an invalid source host, as #275 made it, and its existing test is unchanged.
%2523 is still accepted: it decodes once to %23, which the source URL keeps as part of the path.
A signature does not get such a URL through: the URL is refused before its signature is read. One of the new tests signs the exact source path with the # to show it.
The first commit adds the three tests through the image route, which the second makes pass.
Judgement call: the refusal reuses ErrInvalidPath with a reason after it, rather than a new error.
Unverified: the %2523 point is read from the code; no test covers it.
Model: opus-5-5
Closes https://git.eeqj.de/sneak/pixa/issues/286.
A `#` written as `%23` in the source path or source query of a `/v1/image/` URL reached `parseImageComponents` decoded and stayed there. The source URL built from it ended at the `#` when it was fetched, so each text after it fetched the same upstream image (without a signature for an allowlisted host) and cached it under keys of its own. Such a URL is now refused with 400, and the rule in `README.md` for writing a source path and query says neither may hold a `#`.
What the diff does not show:
- The check runs 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, and its existing test is unchanged.
- `%2523` is still accepted: it decodes once to `%23`, which the source URL keeps as part of the path.
- A signature does not get such a URL through: the URL is refused before its signature is read. One of the new tests signs the exact source path with the `#` to show it.
The first commit adds the three tests through the image route, which the second makes pass.
Judgement call: the refusal reuses `ErrInvalidPath` with a reason after it, rather than a new error.
Unverified: the `%2523` point is read from the code; no test covers it.
Model: opus-5-5
The image route serves these today: the source URL ends at the #, so
each text after it fetches the same upstream image and caches it under
keys of its own.
Model: opus-5-5
A # written as %23 reached the parser decoded and stayed in the source
path or query. The source URL built from it ended at the # when it was
fetched, so each text after it fetched the same upstream image, without
a signature for an allowlisted host, and cached it under keys of its
own. Such a URL is now refused with 400, and README.md says neither the
source path nor the source query may hold a #.
Model: opus-5-5
clawbot
self-assigned this 2026-10-16 01:22:31 +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.
Closes #286.
A
#written as%23in the source path or source query of a/v1/image/URL reachedparseImageComponentsdecoded and stayed there. The source URL built from it ended at the#when it was fetched, so each text after it fetched the same upstream image (without a signature for an allowlisted host) and cached it under keys of its own. Such a URL is now refused with 400, and the rule inREADME.mdfor writing a source path and query says neither may hold a#.What the diff does not show:
%23in the host is still refused as an invalid source host, as #275 made it, and its existing test is unchanged.%2523is still accepted: it decodes once to%23, which the source URL keeps as part of the path.#to show it.The first commit adds the three tests through the image route, which the second makes pass.
Judgement call: the refusal reuses
ErrInvalidPathwith a reason after it, rather than a new error.Unverified: the
%2523point is read from the code; no test covers it.Model: opus-5-5
Review passed.
Model: opus-5-5