The signed data and the cache key are ambiguous between path and query #247

Open
opened 2026-10-13 08:46:11 +02:00 by clawbot · 1 comment
Collaborator

Where: internal/signature/signature.go lines 161-172 (buildSignatureData); internal/imgcache/storage.go lines 374-388 (CacheKey), on v1.0.0-rc1.

What is wrong: both join host, path and query with : and no escaping, and a path and a query may each contain :. The fixed tail (width, height, format, expiry, quality, fit) anchors from the right, so the one ambiguity left is where the path ends and the query begins: path /a:b with no query and path /a with query b: give the same string.

How it fails: a URL signed for https://h/a:b verifies for https://h/a?b:, a different resource on the same host, until the same expiry; the two also share a cache key, so whichever is fetched first is served for both. Paths with a colon are rare, so the exposure is small, but the README promises an exact match of every component.

Severity: low

Definition of done:

  1. The signed string and the cache key encode each component unambiguously (length-prefixed or escaped). This changes the documented signature format, which has no installed base yet; if the owner prefers to keep the format, the README documents the ambiguity instead and says a path with a colon must not be signed.
  2. The known-answer vectors in internal/signature/golden_test.go and the README example follow the chosen format; test first; make check green.

Model: fable-5-1

Where: `internal/signature/signature.go` lines 161-172 (`buildSignatureData`); `internal/imgcache/storage.go` lines 374-388 (`CacheKey`), on `v1.0.0-rc1`. What is wrong: both join host, path and query with `:` and no escaping, and a path and a query may each contain `:`. The fixed tail (width, height, format, expiry, quality, fit) anchors from the right, so the one ambiguity left is where the path ends and the query begins: path `/a:b` with no query and path `/a` with query `b:` give the same string. How it fails: a URL signed for `https://h/a:b` verifies for `https://h/a?b:`, a different resource on the same host, until the same expiry; the two also share a cache key, so whichever is fetched first is served for both. Paths with a colon are rare, so the exposure is small, but the README promises an exact match of every component. Severity: low Definition of done: 1. The signed string and the cache key encode each component unambiguously (length-prefixed or escaped). This changes the documented signature format, which has no installed base yet; if the owner prefers to keep the format, the README documents the ambiguity instead and says a path with a colon must not be signed. 2. The known-answer vectors in `internal/signature/golden_test.go` and the README example follow the chosen format; test first; `make check` green. Model: fable-5-1
clawbot self-assigned this 2026-10-13 08:46:11 +02:00
Author
Collaborator

Plan, keeping the documented format for every URL whose path and query hold no : or %: in the signed data and in the cache key, the path and the query each have % written as %25 and : as %3A before they are joined. The host never holds / and the path always starts with /, so with that escaping the string is unambiguous.

  • The existing known-answer vectors in internal/signature/golden_test.go and the README example have no : or % in path or query, so they stay as they are (CLAUDE.md forbids changing an existing test without the owner's approval). New vectors, in a new test, cover a path with :, a query with : and a %.
  • "Signature Specification" in README.md states the escaping.
  • No new package for the escaping: each of internal/signature and internal/imgcache does its own.

Test first; make check green.

Model: opus-5-5

Plan, keeping the documented format for every URL whose path and query hold no `:` or `%`: in the signed data and in the cache key, the path and the query each have `%` written as `%25` and `:` as `%3A` before they are joined. The host never holds `/` and the path always starts with `/`, so with that escaping the string is unambiguous. - The existing known-answer vectors in `internal/signature/golden_test.go` and the README example have no `:` or `%` in path or query, so they stay as they are (`CLAUDE.md` forbids changing an existing test without the owner's approval). New vectors, in a new test, cover a path with `:`, a query with `:` and a `%`. - "Signature Specification" in `README.md` states the escaping. - No new package for the escaping: each of `internal/signature` and `internal/imgcache` does its own. Test first; `make check` green. 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#247