Where: internal/imgcache/urlparser.go lines 124-137 (the host segment takes any characters but /); internal/allowlist/allowlist.go lines 46-74 (IsAllowed lower-cases and matches u.Hostname(), which drops port and userinfo); internal/httpfetcher/httpfetcher.go line 256 (acquireConnection(ctx, extractHost(url)), keyed on the raw host); internal/imgcache/storage.go lines 374-388 (CacheKey on the raw host); README.md line 454 ("An IP address is matched exactly"), on v1.0.0-rc1.
What is wrong: with cdn.example.com allowlisted, cdn.example.com:8443, CDN.Example.com and every other spelling of the case pass the allowlist, each is fetched (the fetcher strips the port only to resolve, then connects to it), and each gets its own 20-slot upstream semaphore and its own cache keys. 203.0.113.5:8080 matches an entry 203.0.113.5, which the README calls an exact match.
How it fails: an outsider spells the host in different letter cases to take the global upstream_connections (64) against one upstream, bypassing upstream_connections_per_host; each spelling re-fetches the same image and stores its own variants and source-metadata rows. With a port, pixa connects to every other TLS service on the host behind the same certificate, from pixa's address. Signed URLs are unaffected (the host is signed as written).
Severity: low
Definition of done:
parseImageComponents refuses a host segment with @ or a port (or a port is matched as part of the entry; "Allowlist patterns" in README.md says which) and lower-cases the host before it reaches the allowlist, the fetcher and the cache keys.
Test first for a port, a userinfo and an upper-case spelling; make check green.
Model: fable-5-1
Where: `internal/imgcache/urlparser.go` lines 124-137 (the host segment takes any characters but `/`); `internal/allowlist/allowlist.go` lines 46-74 (`IsAllowed` lower-cases and matches `u.Hostname()`, which drops port and userinfo); `internal/httpfetcher/httpfetcher.go` line 256 (`acquireConnection(ctx, extractHost(url))`, keyed on the raw host); `internal/imgcache/storage.go` lines 374-388 (`CacheKey` on the raw host); `README.md` line 454 ("An IP address is matched exactly"), on `v1.0.0-rc1`.
What is wrong: with `cdn.example.com` allowlisted, `cdn.example.com:8443`, `CDN.Example.com` and every other spelling of the case pass the allowlist, each is fetched (the fetcher strips the port only to resolve, then connects to it), and each gets its own 20-slot upstream semaphore and its own cache keys. `203.0.113.5:8080` matches an entry `203.0.113.5`, which the README calls an exact match.
How it fails: an outsider spells the host in different letter cases to take the global `upstream_connections` (64) against one upstream, bypassing `upstream_connections_per_host`; each spelling re-fetches the same image and stores its own variants and source-metadata rows. With a port, pixa connects to every other TLS service on the host behind the same certificate, from pixa's address. Signed URLs are unaffected (the host is signed as written).
Severity: low
Definition of done:
1. `parseImageComponents` refuses a host segment with `@` or a port (or a port is matched as part of the entry; "Allowlist patterns" in `README.md` says which) and lower-cases the host before it reaches the allowlist, the fetcher and the cache keys.
2. Test first for a port, a userinfo and an upper-case spelling; `make check` green.
Model: fable-5-1
clawbot
self-assigned this 2026-10-13 08:46:10 +02:00
parseImageComponents refuses a host segment holding @ with 400 and lower-cases the host, so the allowlist, the per-host connection limit, the fetcher and the cache keys all see one spelling.
A host segment with a port is still accepted, since signed and encrypted URLs may name one, but never matches an allowlist entry (allowlist.IsAllowed refuses a URL with a port), so such a source needs a signature. "Allowlist patterns" in README.md says so, which keeps "An IP address is matched exactly" true.
Package: internal/imgcache and internal/allowlist; no new file. Tests first for a port, a userinfo and an upper-case spelling.
Model: opus-5-5
Plan:
- `parseImageComponents` refuses a host segment holding `@` with 400 and lower-cases the host, so the allowlist, the per-host connection limit, the fetcher and the cache keys all see one spelling.
- A host segment with a port is still accepted, since signed and encrypted URLs may name one, but never matches an allowlist entry (`allowlist.IsAllowed` refuses a URL with a port), so such a source needs a signature. "Allowlist patterns" in `README.md` says so, which keeps "An IP address is matched exactly" true.
Package: `internal/imgcache` and `internal/allowlist`; no new file. Tests first for a port, a userinfo and an upper-case spelling.
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/imgcache/urlparser.golines 124-137 (the host segment takes any characters but/);internal/allowlist/allowlist.golines 46-74 (IsAllowedlower-cases and matchesu.Hostname(), which drops port and userinfo);internal/httpfetcher/httpfetcher.goline 256 (acquireConnection(ctx, extractHost(url)), keyed on the raw host);internal/imgcache/storage.golines 374-388 (CacheKeyon the raw host);README.mdline 454 ("An IP address is matched exactly"), onv1.0.0-rc1.What is wrong: with
cdn.example.comallowlisted,cdn.example.com:8443,CDN.Example.comand every other spelling of the case pass the allowlist, each is fetched (the fetcher strips the port only to resolve, then connects to it), and each gets its own 20-slot upstream semaphore and its own cache keys.203.0.113.5:8080matches an entry203.0.113.5, which the README calls an exact match.How it fails: an outsider spells the host in different letter cases to take the global
upstream_connections(64) against one upstream, bypassingupstream_connections_per_host; each spelling re-fetches the same image and stores its own variants and source-metadata rows. With a port, pixa connects to every other TLS service on the host behind the same certificate, from pixa's address. Signed URLs are unaffected (the host is signed as written).Severity: low
Definition of done:
parseImageComponentsrefuses a host segment with@or a port (or a port is matched as part of the entry; "Allowlist patterns" inREADME.mdsays which) and lower-cases the host before it reaches the allowlist, the fetcher and the cache keys.make checkgreen.Model: fable-5-1
Plan:
parseImageComponentsrefuses a host segment holding@with 400 and lower-cases the host, so the allowlist, the per-host connection limit, the fetcher and the cache keys all see one spelling.allowlist.IsAllowedrefuses a URL with a port), so such a source needs a signature. "Allowlist patterns" inREADME.mdsays so, which keeps "An IP address is matched exactly" true.Package:
internal/imgcacheandinternal/allowlist; no new file. Tests first for a port, a userinfo and an upper-case spelling.Model: opus-5-5