From 6453b9a1250e611dc022731d70b015110a4cdac6 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 15 Oct 2026 10:03:47 +0000 Subject: [PATCH] Say the source host is signed as the URL writes it (part of #246) The Signature Specification read as though the hostname were signed with ":" and the port appended. The code signs the host segment as the URL writes it up to the first "/", lower-cased, so IPv6 brackets and a ":" with or without a port are kept. The README line, the TODO.md entry and the parser comment now say so. Model: opus-5-5 --- README.md | 5 +++-- TODO.md | 4 ++-- internal/imgcache/urlparser.go | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 687e737..609ad9e 100644 --- a/README.md +++ b/README.md @@ -418,8 +418,9 @@ HMAC-SHA256(secret, "host:path:query:width:height:format:expiration:quality:fit" Where: -- `host` — source origin hostname in lower case, whatever its case in the URL, - followed by `:` and the port when the URL has one (e.g. `cdn.example.com`) +- `host` — source host as the URL writes it up to the first `/`, in lower case, + keeping an IPv6 address's brackets and any `:` and port after it (e.g. + `cdn.example.com`, `cdn.example.com:8443`, `[2001:db8::1]:8443`) - `path` — source path (e.g. `/photos/cat.jpg`), with each `%` written as `%25` and each `:` as `%3A`, so `/photos/a:b.jpg` is signed as `/photos/a%3Ab.jpg` - `query` — source query string, empty string if none, with `%` and `:` written diff --git a/TODO.md b/TODO.md index 3285f97..d1ccf51 100644 --- a/TODO.md +++ b/TODO.md @@ -39,8 +39,8 @@ P2: security: per-origin rate limiting password, with 400. A host with a port, or with a `:` and no port, needs a signature even when it is allowlisted. That is checked in the image service, not in `allowlist.IsAllowed`, which also matches `referer_blocklist`, where a - `Referer` is blocked whatever its port. A URL is signed over the lower-cased - host. + `Referer` is blocked whatever its port. A URL is signed over its host as it + writes it, in lower case, with any IPv6 brackets, `:` and port kept. - 2026-10-15 a `:` in the source path or query no longer moves where the path ends and the query begins (closes #247): the signed data and the cache key joined host, path and query with `:` and no escaping, so a URL signed for diff --git a/internal/imgcache/urlparser.go b/internal/imgcache/urlparser.go index ea5b6fd..4247be6 100644 --- a/internal/imgcache/urlparser.go +++ b/internal/imgcache/urlparser.go @@ -151,7 +151,8 @@ func parseImageComponents(remainder string) (*ParsedURL, error) { } // Host names do not depend on letter case, so the allowlist, the upstream - // connection limits and the cache see one spelling of each host + // connection limits, the cache and the signature see one spelling of each + // host host = strings.ToLower(host) // Extract query string if present in path