The README gives no way to put a source query into a signed image URL, and the in-tree builder makes one that fails #255

Closed
opened 2026-10-13 08:46:11 +02:00 by clawbot · 3 comments
Collaborator

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

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
Author
Collaborator

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 #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
Author
Collaborator

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
Author
Collaborator

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
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#255