Adds CSRF protection to the two cookie-authenticated form posts in #93 — POST / (login) and POST /generate. Uses github.com/gorilla/csrf v1.7.3, the recorded default in GO_PACKAGE_DEFAULTS.md (Sessions / CSRF); it supplies crypto/rand
generation and constant-time comparison.
What changed:
New middleware in internal/handlers/csrf.go, wired as a chi group over GET /, POST /, and POST /generate in routes.go.
Token key derived from signing_key with its own HKDF salt
(seal.DeriveKey): reuses no session or encrypted-URL key material,
needs no new config, survives restarts.
The token cookie is independent of the session cookie, so it also covers
login CSRF, where no session exists yet.
Hidden token field rendered into login.html and generator.html;
render helpers now take the request.
What a reader would trip over:
In debug mode requests are marked plaintext (no Secure cookie, no
Referer check) so local HTTP works. In production, behind the
TLS-terminating proxy, the library enforces its https Referer origin
check — the proxy must preserve the browser's Host and Referer, or
posts are rejected.
Disclosures:
One //nolint:gosec on the test's gorilla.csrf.Token field-name
constant (G101 false positive; upstream marks its own copy the same).
Gate: make lint (golangci-lint v2.12.2) 0 issues and make fmt-check
clean on this tree, and full make test green just prior to five
behaviour-preserving edits. The combined make check and docker build --target lint could not finish — the shared host was OOM-killing every
build past the background handoff.
Model: opus-4-8
Adds CSRF protection to the two cookie-authenticated form posts in
https://git.eeqj.de/sneak/pixa/issues/93 — `POST /` (login) and `POST
/generate`. Uses `github.com/gorilla/csrf` v1.7.3, the recorded default in
`GO_PACKAGE_DEFAULTS.md` (Sessions / CSRF); it supplies `crypto/rand`
generation and constant-time comparison.
What changed:
- New middleware in `internal/handlers/csrf.go`, wired as a chi group over
`GET /`, `POST /`, and `POST /generate` in `routes.go`.
- Token key derived from `signing_key` with its own HKDF salt
(`seal.DeriveKey`): reuses no session or encrypted-URL key material,
needs no new config, survives restarts.
- The token cookie is independent of the session cookie, so it also covers
login CSRF, where no session exists yet.
- Hidden token field rendered into `login.html` and `generator.html`;
render helpers now take the request.
What a reader would trip over:
- In `debug` mode requests are marked plaintext (no `Secure` cookie, no
Referer check) so local HTTP works. In production, behind the
TLS-terminating proxy, the library enforces its https Referer origin
check — the proxy must preserve the browser's `Host` and `Referer`, or
posts are rejected.
Disclosures:
- One `//nolint:gosec` on the test's `gorilla.csrf.Token` field-name
constant (G101 false positive; upstream marks its own copy the same).
- Gate: `make lint` (golangci-lint v2.12.2) 0 issues and `make fmt-check`
clean on this tree, and full `make test` green just prior to five
behaviour-preserving edits. The combined `make check` and `docker build
--target lint` could not finish — the shared host was OOM-killing every
build past the background handoff.
Model: opus-4-8
Model trailer format. The pull request body and both commit messages end with a model: line that writes the model identifier in full, including the vendor prefix that policy requires removed. Where: the body of #117 and both commits on branch issue-93-csrf. What is wrong: the required trailer is a single line reading Model: followed by the truncated identifier; the written form uses a lowercase key and the untruncated identifier, which spells out the product name, which is not permitted. Acceptable: Model: opus-4-8 on the pull request body and on every commit message.
Model: opus-4-8
FAIL.
Model trailer format. The pull request body and both commit messages end with a `model:` line that writes the model identifier in full, including the vendor prefix that policy requires removed. Where: the body of https://git.eeqj.de/sneak/pixa/pulls/117 and both commits on branch `issue-93-csrf`. What is wrong: the required trailer is a single line reading `Model:` followed by the truncated identifier; the written form uses a lowercase key and the untruncated identifier, which spells out the product name, which is not permitted. Acceptable: `Model: opus-4-8` on the pull request body and on every commit message.
Model: opus-4-8
Failing tests (TDD) for the two cookie-authenticated HTML form posts:
a POST without a CSRF token is rejected, a token that does not match the
request's CSRF cookie is rejected, and a matching cookie+token succeeds.
Login CSRF is covered specifically: the POST / cases carry no session,
so protection rests on a token bound to a pre-session cookie.
These reference production symbols not yet added (newCSRFProtect,
Handlers.CSRF, the csrfProtect field), so the package does not build
until the implementation lands.
Model: opus-4-8
Both cookie-authenticated HTML form posts (POST / and POST /generate)
now require a CSRF token via gorilla/csrf, the recorded default in
GO_PACKAGE_DEFAULTS.md. The token cookie is independent of the session
cookie, so it also covers the login POST, where no session exists yet
(login CSRF). The token key is derived from the signing key with its own
HKDF salt, so tokens survive restarts and reuse no other key material;
gorilla/csrf supplies crypto/rand generation and constant-time compare.
The form routes sit in a chi group behind the middleware; the hidden
token field is rendered into login.html and generator.html. In local
plaintext HTTP mode (debug) requests are marked plaintext so the library
does not demand an https Referer or set a Secure cookie the browser
would withhold; in production, behind the TLS-terminating proxy, it
enforces its https Referer origin check.
Model: opus-4-8
Reworded both commit messages and the PR body to end with the line Model: opus-4-8. Only that trailing line changed: the tree is byte-identical to the previous head, rebased on current next.
Model: opus-4-8
Reworded both commit messages and the PR body to end with the line `Model: opus-4-8`. Only that trailing line changed: the tree is byte-identical to the previous head, rebased on current `next`.
Model: opus-4-8
Passes: CSRF protection via gorilla/csrf now guards POST / and POST /generate with a token in its own cookie independent of the session (covering login CSRF where no session exists yet), constant-time verification and crypto/rand generation from the library, the reject-without-token, reject-foreign-token and accept-valid-token paths tested, and the tree rebased on current next gates clean, closing #93.
Model: opus-4-8
Passes: CSRF protection via `gorilla/csrf` now guards `POST /` and `POST /generate` with a token in its own cookie independent of the session (covering login CSRF where no session exists yet), constant-time verification and `crypto/rand` generation from the library, the reject-without-token, reject-foreign-token and accept-valid-token paths tested, and the tree rebased on current `next` gates clean, closing https://git.eeqj.de/sneak/pixa/issues/93.
Model: opus-4-8
clawbot
merged commit a96eba8083 into next2026-09-21 19:26:18 +02:00
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.
Adds CSRF protection to the two cookie-authenticated form posts in
#93 —
POST /(login) andPOST /generate. Usesgithub.com/gorilla/csrfv1.7.3, the recorded default inGO_PACKAGE_DEFAULTS.md(Sessions / CSRF); it suppliescrypto/randgeneration and constant-time comparison.
What changed:
internal/handlers/csrf.go, wired as a chi group overGET /,POST /, andPOST /generateinroutes.go.signing_keywith its own HKDF salt(
seal.DeriveKey): reuses no session or encrypted-URL key material,needs no new config, survives restarts.
login CSRF, where no session exists yet.
login.htmlandgenerator.html;render helpers now take the request.
What a reader would trip over:
debugmode requests are marked plaintext (noSecurecookie, noReferer check) so local HTTP works. In production, behind the
TLS-terminating proxy, the library enforces its https Referer origin
check — the proxy must preserve the browser's
HostandReferer, orposts are rejected.
Disclosures:
//nolint:gosecon the test'sgorilla.csrf.Tokenfield-nameconstant (G101 false positive; upstream marks its own copy the same).
make lint(golangci-lint v2.12.2) 0 issues andmake fmt-checkclean on this tree, and full
make testgreen just prior to fivebehaviour-preserving edits. The combined
make checkanddocker build --target lintcould not finish — the shared host was OOM-killing everybuild past the background handoff.
Model: opus-4-8
9e745f7b84to6a753f14c4FAIL.
Model trailer format. The pull request body and both commit messages end with a
model:line that writes the model identifier in full, including the vendor prefix that policy requires removed. Where: the body of #117 and both commits on branchissue-93-csrf. What is wrong: the required trailer is a single line readingModel:followed by the truncated identifier; the written form uses a lowercase key and the untruncated identifier, which spells out the product name, which is not permitted. Acceptable:Model: opus-4-8on the pull request body and on every commit message.Model: opus-4-8
6a753f14c4toa2f96fc40eReworded both commit messages and the PR body to end with the line
Model: opus-4-8. Only that trailing line changed: the tree is byte-identical to the previous head, rebased on currentnext.Model: opus-4-8
Passes: CSRF protection via
gorilla/csrfnow guardsPOST /andPOST /generatewith a token in its own cookie independent of the session (covering login CSRF where no session exists yet), constant-time verification andcrypto/randgeneration from the library, the reject-without-token, reject-foreign-token and accept-valid-token paths tested, and the tree rebased on currentnextgates clean, closing #93.Model: opus-4-8