The metrics middleware labelled each request with its path, so every
new path added series to the registry that were never removed, and
/metrics listed the token of every encrypted URL requested. The handler
label is now the pattern of the chi route the request will match, looked
up before routing, or "unmatched" when none does. /v1 and /v1/ ask the
image subrouter, and an empty path is looked up as /, as chi routes it.
TestMetricsRecordsServedRequest expects "unmatched" in place of "/test",
as the owner approved.
Model: opus-5-5
script-src and style-src now allow only 'self'. The generator page's
two inline onclick handlers, which selected the generated URL and
copied it, move into internal/static/generator.js and are attached
with addEventListener. The bundled Tailwind script, which built styles
in the browser and injected them at runtime, is replaced by a small
hand-written internal/static/style.css holding only the rules the
login and generator pages use; the templates carry a few plain class
names in place of Tailwind's. No build step. The pages keep their
layout, not every pixel of it.
Model: opus-5-5
New tests only. MetricsAuth on its own answers 401 with a challenge
without credentials or with a wrong username or password, and lets the
configured ones through. A CORS preflight request gets the same
Access-Control-Allow-Origin as a GET. A POST / carrying the signing key
leaves the key out of the request log line, and the login handler's own
log lines leave out the submitted key. The metrics middleware on its own
records a request it served; the router records nothing while no metrics
username is set.
Not tested through the router: the basic auth in front of /metrics and
recording with a metrics username set (#180).
The pinned basicauth-go compares the password in constant time.
Model: opus-5-5
README.md documented access_control_allow_origin,
upstream_fetch_timeout, upstream_max_response_size and
downstream_timeout, but pixa did not know them, so a config following
the README aborted startup. Each is now a setting with its PIXA_
variable, defaulting to the value that was fixed in the code: *, 30s,
50 MiB and 60s. Durations are Go duration strings and must be positive;
the size is whole bytes, at most 1 GiB. The origin is * or one http or
https origin written exactly as a browser sends it; anything else
aborts startup. downstream_timeout sets both the server's write timeout
and the per-request timeout. The owner approved the edits to existing
tests.
Model: opus-5-5
SecurityHeaders() now also sets Strict-Transport-Security (one year, includeSubDomains), a Content-Security-Policy (default-src self, frame-ancestors none) and a Permissions-Policy denying the browser features pixa does not use. X-Frame-Options stays as the legacy fallback.
What a reader would trip over: HSTS is sent on every response even though pixa listens on plain HTTP behind a TLS-terminating proxy; browsers ignore the header over plaintext, and this avoids trusting a forwarded-proto header. The clipboard feature is left unlisted so the copy button on the generator page keeps working.
Disclosure: script-src and style-src carry unsafe-inline because the generator template has inline onclick handlers and the bundled Tailwind script injects a style element at runtime; removing it needs template changes and is tracked separately.
Model: opus-4-8 (implementation, review); fable-5-1 (landing message)
Accumulating milestone branch. One squashed commit per closed issue; `next` is kept green and mergeable to `main` at any time without notice.
Landed so far:
- `chore: update golangci-lint to v2.12.2 with canonical config` (#54) — canonical v2-schema `.golangci.yml`, pins bumped in `Dockerfile` and `script/bootstrap`, tree at `0 issues.`. Three behaviour deltas are recorded in that PR's body: `Cache.StoreVariant` takes a context, `MetadataStorage.Store` no longer leaks temp files on failure, and the `signing_key` too-short error text gained a `value too short:` prefix.
Sequencing for the milestone is tracked in #103.
Reviewed-on: #105
Co-authored-by: clawbot <clawbot@noreply.example.org>