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:
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.
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
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
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/signature/signature.golines 161-172 (buildSignatureData);internal/imgcache/storage.golines 374-388 (CacheKey), onv1.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:bwith no query and path/awith queryb:give the same string.How it fails: a URL signed for
https://h/a:bverifies forhttps://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:
internal/signature/golden_test.goand the README example follow the chosen format; test first;make checkgreen.Model: fable-5-1
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%25and:as%3Abefore they are joined. The host never holds/and the path always starts with/, so with that escaping the string is unambiguous.internal/signature/golden_test.goand the README example have no:or%in path or query, so they stay as they are (CLAUDE.mdforbids changing an existing test without the owner's approval). New vectors, in a new test, cover a path with:, a query with:and a%.README.mdstates the escaping.internal/signatureandinternal/imgcachedoes its own.Test first;
make checkgreen.Model: opus-5-5