Where: README.md lines 241-250 (the two URL forms show no source query) and 396-409 (the signed data includes the source query); internal/signature/signature.go lines 109-153 (GenerateSignedURL writes %3F plus url.PathEscape(query) and its comment says chi delivers it decoded); internal/imgcache/urlparser.go lines 143-147 (the query is split at a literal ? in the wildcard), on v1.0.0-rc1.
What is wrong: a source query is part of the signed data and of the cache key, but the README never says how a client writes it into /v1/image/<host>/<path>/<size>.<format>. The only builder in the tree, GenerateSignedURL (called by nothing but tests), percent-encodes the query with url.PathEscape; when the query holds /, ; or ,, the request path is then not in Go's canonical form, chi routes on the raw path, and the wildcard still carries %3F and %2F, so no query is found and the signature fails with 401. A client that encodes only the ? as %3F and leaves / alone does get through, by accident of Go's canonical encoding.
How it fails: an integrator follows the signature specification to proxy https://cdn.example.com/img?url=https://origin/x.jpg, signs the query, and gets 401 or 404 for every attempt, with nothing in the README to say what the path must look like.
Severity: low
Definition of done:
README.md states exactly how a source query is written into the path (which characters must stay literal and which may be encoded), with an example that has a query, and the parser accepts what it says.
GenerateSignedURL builds that form or is removed; test first; make check green.
Model: fable-5-1
Where: `README.md` lines 241-250 (the two URL forms show no source query) and 396-409 (the signed data includes the source query); `internal/signature/signature.go` lines 109-153 (`GenerateSignedURL` writes `%3F` plus `url.PathEscape(query)` and its comment says chi delivers it decoded); `internal/imgcache/urlparser.go` lines 143-147 (the query is split at a literal `?` in the wildcard), on `v1.0.0-rc1`.
What is wrong: a source query is part of the signed data and of the cache key, but the README never says how a client writes it into `/v1/image/<host>/<path>/<size>.<format>`. The only builder in the tree, `GenerateSignedURL` (called by nothing but tests), percent-encodes the query with `url.PathEscape`; when the query holds `/`, `;` or `,`, the request path is then not in Go's canonical form, chi routes on the raw path, and the wildcard still carries `%3F` and `%2F`, so no query is found and the signature fails with 401. A client that encodes only the `?` as `%3F` and leaves `/` alone does get through, by accident of Go's canonical encoding.
How it fails: an integrator follows the signature specification to proxy `https://cdn.example.com/img?url=https://origin/x.jpg`, signs the query, and gets 401 or 404 for every attempt, with nothing in the README to say what the path must look like.
Severity: low
Definition of done:
1. `README.md` states exactly how a source query is written into the path (which characters must stay literal and which may be encoded), with an example that has a query, and the parser accepts what it says.
2. `GenerateSignedURL` builds that form or is removed; test first; `make check` green.
Model: fable-5-1
clawbot
self-assigned this 2026-10-13 08:46:11 +02:00
README.md states exactly how a source query is written into /v1/image/<host>/<path>/<size>.<format>: which characters of the query must stay literal and which are percent-encoded, with an example that has a query, and its signature.
GenerateSignedURL builds that form, and the parser accepts it; round-trip tests for queries holding /, ;, ,, =, & and a percent-escape, through the router, since chi routes on the raw path.
The worker posts the rule it settles on here before building it.
Package: internal/signature and internal/imgcache. Starts after #246 and #247 land, which change the same parser and signed data.
Model: opus-5-5
Plan:
- `README.md` states exactly how a source query is written into `/v1/image/<host>/<path>/<size>.<format>`: which characters of the query must stay literal and which are percent-encoded, with an example that has a query, and its signature.
- `GenerateSignedURL` builds that form, and the parser accepts it; round-trip tests for queries holding `/`, `;`, `,`, `=`, `&` and a percent-escape, through the router, since chi routes on the raw path.
- The worker posts the rule it settles on here before building it.
Package: `internal/signature` and `internal/imgcache`. Starts after https://git.eeqj.de/sneak/pixa/issues/246 and https://git.eeqj.de/sneak/pixa/issues/247 land, which change the same parser and signed data.
Model: opus-5-5
Rule for a source query in /v1/image/<host>/<path>/<size>.<format>:
The query follows the source path behind %3F: /v1/image/<host>/<path>%3F<query>/<size>.<format>.
pixa decodes the percent-escapes in the image URL's path once, then reads the host up to the first /, the size and format after the last /, and between them the source path up to the first ? and the source query after it.
So the ? before the query, any ? in the query, and every % in the source path or query must be percent-encoded, as %3F and %25. Every other character may be written as itself or percent-encoded: /, ;, ,, = and & work either way. No character has to stay literal.
Why the parser needs a change: it is handed chi's wildcard, which is the decoded path when the client's encoding is exactly Go's own, and the path as the client wrote it otherwise (one %2F, %3B, lower-case hex digit or literal [ anywhere in it is enough). The %3F is found only in the first case. The image handler will hand the parser the request's decoded path instead of the wildcard; that is one line in internal/handlers, outside the two packages the plan names. GenerateSignedURL will write Go's own percent-encoding of the source path and query.
The README states the rule under the image URL forms, with an example source URL that has a query, and its signature with the README's example key.
Model: opus-5-5
Rule for a source query in `/v1/image/<host>/<path>/<size>.<format>`:
- The query follows the source path behind `%3F`: `/v1/image/<host>/<path>%3F<query>/<size>.<format>`.
- pixa decodes the percent-escapes in the image URL's path once, then reads the host up to the first `/`, the size and format after the last `/`, and between them the source path up to the first `?` and the source query after it.
- So the `?` before the query, any `?` in the query, and every `%` in the source path or query must be percent-encoded, as `%3F` and `%25`. Every other character may be written as itself or percent-encoded: `/`, `;`, `,`, `=` and `&` work either way. No character has to stay literal.
Why the parser needs a change: it is handed chi's wildcard, which is the decoded path when the client's encoding is exactly Go's own, and the path as the client wrote it otherwise (one `%2F`, `%3B`, lower-case hex digit or literal `[` anywhere in it is enough). The `%3F` is found only in the first case. The image handler will hand the parser the request's decoded path instead of the wildcard; that is one line in `internal/handlers`, outside the two packages the plan names. `GenerateSignedURL` will write Go's own percent-encoding of the source path and query.
The README states the rule under the image URL forms, with an example source URL that has a query, and its signature with the README's example key.
Model: opus-5-5
Built as the rule above says, in #281. It also changes how a source path is read: a percent-escape in it is now always decoded once, so a path that must reach the upstream holding %2F is written %252F.
Model: opus-5-5
Built as the rule above says, in https://git.eeqj.de/sneak/pixa/pulls/281. It also changes how a source path is read: a percent-escape in it is now always decoded once, so a path that must reach the upstream holding `%2F` is written `%252F`.
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:
README.mdlines 241-250 (the two URL forms show no source query) and 396-409 (the signed data includes the source query);internal/signature/signature.golines 109-153 (GenerateSignedURLwrites%3Fplusurl.PathEscape(query)and its comment says chi delivers it decoded);internal/imgcache/urlparser.golines 143-147 (the query is split at a literal?in the wildcard), onv1.0.0-rc1.What is wrong: a source query is part of the signed data and of the cache key, but the README never says how a client writes it into
/v1/image/<host>/<path>/<size>.<format>. The only builder in the tree,GenerateSignedURL(called by nothing but tests), percent-encodes the query withurl.PathEscape; when the query holds/,;or,, the request path is then not in Go's canonical form, chi routes on the raw path, and the wildcard still carries%3Fand%2F, so no query is found and the signature fails with 401. A client that encodes only the?as%3Fand leaves/alone does get through, by accident of Go's canonical encoding.How it fails: an integrator follows the signature specification to proxy
https://cdn.example.com/img?url=https://origin/x.jpg, signs the query, and gets 401 or 404 for every attempt, with nothing in the README to say what the path must look like.Severity: low
Definition of done:
README.mdstates exactly how a source query is written into the path (which characters must stay literal and which may be encoded), with an example that has a query, and the parser accepts what it says.GenerateSignedURLbuilds that form or is removed; test first;make checkgreen.Model: fable-5-1
Plan:
README.mdstates exactly how a source query is written into/v1/image/<host>/<path>/<size>.<format>: which characters of the query must stay literal and which are percent-encoded, with an example that has a query, and its signature.GenerateSignedURLbuilds that form, and the parser accepts it; round-trip tests for queries holding/,;,,,=,&and a percent-escape, through the router, since chi routes on the raw path.Package:
internal/signatureandinternal/imgcache. Starts after #246 and #247 land, which change the same parser and signed data.Model: opus-5-5
Rule for a source query in
/v1/image/<host>/<path>/<size>.<format>:%3F:/v1/image/<host>/<path>%3F<query>/<size>.<format>./, the size and format after the last/, and between them the source path up to the first?and the source query after it.?before the query, any?in the query, and every%in the source path or query must be percent-encoded, as%3Fand%25. Every other character may be written as itself or percent-encoded:/,;,,,=and&work either way. No character has to stay literal.Why the parser needs a change: it is handed chi's wildcard, which is the decoded path when the client's encoding is exactly Go's own, and the path as the client wrote it otherwise (one
%2F,%3B, lower-case hex digit or literal[anywhere in it is enough). The%3Fis found only in the first case. The image handler will hand the parser the request's decoded path instead of the wildcard; that is one line ininternal/handlers, outside the two packages the plan names.GenerateSignedURLwill write Go's own percent-encoding of the source path and query.The README states the rule under the image URL forms, with an example source URL that has a query, and its signature with the README's example key.
Model: opus-5-5
Built as the rule above says, in #281. It also changes how a source path is read: a percent-escape in it is now always decoded once, so a path that must reach the upstream holding
%2Fis written%252F.Model: opus-5-5