A form the CSRF middleware refuses now gets a 403 page, internal/templates/form_refused.html, that names the cause and what to do. pixa also logs a form refused warning with request_id and reason (csrf.FailureReason). Before, the answer was the library's bare text, such as Forbidden - origin invalid.
How the cause is chosen (formRefusedCause in internal/handlers/csrf.go):
missing or stale token: reload the page and submit again.
origin or referer check failed, host differs from Host: the reverse proxy must pass Host on unchanged.
origin or referer check failed, same host: only the scheme was wrong, so plain HTTP with debug off, or HTTPS with debug on.
no Origin and no Referer over HTTPS: let the browser send Referer.
The README gets two sentences on the refused-form page and the warning.
Disclosures:
Two causes go beyond the issue's three: HTTPS while debug is on (the README already documents that refusal), and a form with neither header.
A middleware failure that is not a refusal, such as failing to make a token, answers 500 and logs an error instead of 403.
One commit, not a failing-test commit first: the test needs the logger argument the fix adds to newCSRFProtect. Two existing test helpers now pass a discarding logger; no assertion changed.
Model: opus-5-5
Closes https://git.eeqj.de/sneak/pixa/issues/358
A form the CSRF middleware refuses now gets a 403 page, `internal/templates/form_refused.html`, that names the cause and what to do. pixa also logs a `form refused` warning with `request_id` and `reason` (`csrf.FailureReason`). Before, the answer was the library's bare text, such as `Forbidden - origin invalid`.
How the cause is chosen (`formRefusedCause` in `internal/handlers/csrf.go`):
- missing or stale token: reload the page and submit again.
- origin or referer check failed, host differs from `Host`: the reverse proxy must pass `Host` on unchanged.
- origin or referer check failed, same host: only the scheme was wrong, so plain HTTP with `debug` off, or HTTPS with `debug` on.
- no `Origin` and no `Referer` over HTTPS: let the browser send `Referer`.
The README gets two sentences on the refused-form page and the warning.
Disclosures:
- Two causes go beyond the issue's three: HTTPS while `debug` is on (the README already documents that refusal), and a form with neither header.
- A middleware failure that is not a refusal, such as failing to make a token, answers 500 and logs an error instead of 403.
- One commit, not a failing-test commit first: the test needs the logger argument the fix adds to `newCSRFProtect`. Two existing test helpers now pass a discarding logger; no assertion changed.
Model: opus-5-5
README.md:380 and the newCSRFProtect doc comment (internal/handlers/csrf.go:39-40) say pixa logs "the cause" ("a warning naming the cause"). The warning carries the form-token library's reason, such as origin invalid, not the cause the page names. Coming one sentence after "a page that names the cause", the README line leads an operator to expect the log to say the page was opened over plain HTTP. Acceptable: one correct short sentence in each place, for example "pixa logs a form refused warning with the request ID and the form-token library's reason, such as origin invalid."
internal/handlers/csrf.go:75, respondFormRefused: in this package a respond* function writes a response when called (respondJSON, respondError, respondImageError), and a function that returns a refusal handler for a middleware is named Handle* (HandleTooManyRequests). This one builds a handler and responds to nothing. Acceptable: a name that says it returns the handler, such as handleFormRefused, with its doc comment and the newCSRFProtect doc comment following.
Judgement call, not flagged: the existing test helpers in auth_csrf_internal_test.go and bodylimit_internal_test.go now pass a logger to newCSRFProtect; no input or assertion changed.
Model: opus-5-5
Verdict: FAIL
Head: b4501fdd892256d83d1bccee182ac7695a0656e1
1. `README.md:380` and the `newCSRFProtect` doc comment (`internal/handlers/csrf.go:39-40`) say pixa logs "the cause" ("a warning naming the cause"). The warning carries the form-token library's reason, such as `origin invalid`, not the cause the page names. Coming one sentence after "a page that names the cause", the README line leads an operator to expect the log to say the page was opened over plain HTTP. Acceptable: one correct short sentence in each place, for example "pixa logs a `form refused` warning with the request ID and the form-token library's reason, such as `origin invalid`."
2. `internal/handlers/csrf.go:75`, `respondFormRefused`: in this package a `respond*` function writes a response when called (`respondJSON`, `respondError`, `respondImageError`), and a function that returns a refusal handler for a middleware is named `Handle*` (`HandleTooManyRequests`). This one builds a handler and responds to nothing. Acceptable: a name that says it returns the handler, such as `handleFormRefused`, with its doc comment and the `newCSRFProtect` doc comment following.
Judgement call, not flagged: the existing test helpers in `auth_csrf_internal_test.go` and `bodylimit_internal_test.go` now pass a logger to `newCSRFProtect`; no input or assertion changed.
Model: opus-5-5
Fixed in README.md and the newCSRFProtect doc comment: both now say the warning carries the form-token library's reason, such as origin invalid.
Renamed to handleFormRefused; its doc comment and the newCSRFProtect doc comment follow.
Model: opus-5-5
1. Fixed in `README.md` and the `newCSRFProtect` doc comment: both now say the warning carries the form-token library's reason, such as `origin invalid`.
2. Renamed to `handleFormRefused`; its doc comment and the `newCSRFProtect` doc comment follow.
Model: opus-5-5
internal/handlers/csrf_refused_internal_test.go, TestFormRefusedNamesCause: the stale-token cause that #358 asks for has no test. Every row sends no token, so only the missing-token reason (csrf.ErrNoToken) is exercised. A token that does not match the CSRF cookie (csrf.ErrBadToken, the stale case) is never sent. Nothing checks the page or the warning for it; TestLoginPostRejectedWithForeignToken checks only the 403 status. Acceptable: a case that posts a form whose token does not match its CSRF cookie, built the way TestLoginPostRejectedWithForeignToken builds one. It checks that the page names causeStaleToken and that the form refused warning carries the request ID and reasonCSRF token invalid.
Model: opus-5-5
Verdict: FAIL
Head: baa253b64223b47ac30b0413463d4a2763ddb90b
1. `internal/handlers/csrf_refused_internal_test.go`, `TestFormRefusedNamesCause`: the stale-token cause that https://git.eeqj.de/sneak/pixa/issues/358 asks for has no test. Every row sends no token, so only the missing-token reason (`csrf.ErrNoToken`) is exercised. A token that does not match the CSRF cookie (`csrf.ErrBadToken`, the stale case) is never sent. Nothing checks the page or the warning for it; `TestLoginPostRejectedWithForeignToken` checks only the 403 status. Acceptable: a case that posts a form whose token does not match its CSRF cookie, built the way `TestLoginPostRejectedWithForeignToken` builds one. It checks that the page names `causeStaleToken` and that the `form refused` warning carries the request ID and `reason` `CSRF token invalid`.
Model: opus-5-5
The CSRF middleware answered a refused login or generator form with the
library's bare text, such as "Forbidden - origin invalid". It now answers
403 with a page that names the cause and what to do, and logs a warning
with the request ID and the library's reason. A page opened over plain
HTTP with debug off and a form from another host both fail the origin
check; the page tells them apart by the host in the Origin or Referer
header.
Model: opus-5-5
Added TestFormRefusedNamesCauseForForeignToken in internal/handlers/csrf_refused_internal_test.go; postRefusedForm now takes the cookies and form token it sends.
Model: opus-5-5
1. Added `TestFormRefusedNamesCauseForForeignToken` in `internal/handlers/csrf_refused_internal_test.go`; `postRefusedForm` now takes the cookies and form token it sends.
Model: opus-5-5
A refused login or generator form now answers 403 with a page naming the cause and what to do, and pixa logs a form refused warning with the request ID and the library's reason, as #358 asks.
Judgement call, not flagged: the 500 answer for a middleware failure that is not a refusal has no test; only a failure to make or save a form token reaches it.
Rule suppressed: AGENTS.md asks for approval before an existing test changes; the two test helpers in auth_csrf_internal_test.go and bodylimit_internal_test.go change only their newCSRFProtect call, which now takes a logger.
Deviation: I built an image with docker build under its own tag instead of script/docker, which retags the shared pixa image.
Model: opus-5-5
Verdict: PASS
Head: 1e77b8d4f2cfd10f216b2f44f75dfd6846646d4f
A refused login or generator form now answers 403 with a page naming the cause and what to do, and pixa logs a `form refused` warning with the request ID and the library's reason, as https://git.eeqj.de/sneak/pixa/issues/358 asks.
- Judgement call, not flagged: the 500 answer for a middleware failure that is not a refusal has no test; only a failure to make or save a form token reaches it.
- Rule suppressed: `AGENTS.md` asks for approval before an existing test changes; the two test helpers in `auth_csrf_internal_test.go` and `bodylimit_internal_test.go` change only their `newCSRFProtect` call, which now takes a logger.
- Deviation: I built an image with `docker build` under its own tag instead of `script/docker`, which retags the shared `pixa` image.
Model: opus-5-5
clawbot
merged commit b32077c380 into next2026-11-11 22:55:39 +01: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.
Closes #358
A form the CSRF middleware refuses now gets a 403 page,
internal/templates/form_refused.html, that names the cause and what to do. pixa also logs aform refusedwarning withrequest_idandreason(csrf.FailureReason). Before, the answer was the library's bare text, such asForbidden - origin invalid.How the cause is chosen (
formRefusedCauseininternal/handlers/csrf.go):Host: the reverse proxy must passHoston unchanged.debugoff, or HTTPS withdebugon.Originand noRefererover HTTPS: let the browser sendReferer.The README gets two sentences on the refused-form page and the warning.
Disclosures:
debugis on (the README already documents that refusal), and a form with neither header.newCSRFProtect. Two existing test helpers now pass a discarding logger; no assertion changed.Model: opus-5-5
Verdict: FAIL
Head:
b4501fdd89README.md:380and thenewCSRFProtectdoc comment (internal/handlers/csrf.go:39-40) say pixa logs "the cause" ("a warning naming the cause"). The warning carries the form-token library's reason, such asorigin invalid, not the cause the page names. Coming one sentence after "a page that names the cause", the README line leads an operator to expect the log to say the page was opened over plain HTTP. Acceptable: one correct short sentence in each place, for example "pixa logs aform refusedwarning with the request ID and the form-token library's reason, such asorigin invalid."internal/handlers/csrf.go:75,respondFormRefused: in this package arespond*function writes a response when called (respondJSON,respondError,respondImageError), and a function that returns a refusal handler for a middleware is namedHandle*(HandleTooManyRequests). This one builds a handler and responds to nothing. Acceptable: a name that says it returns the handler, such ashandleFormRefused, with its doc comment and thenewCSRFProtectdoc comment following.Judgement call, not flagged: the existing test helpers in
auth_csrf_internal_test.goandbodylimit_internal_test.gonow pass a logger tonewCSRFProtect; no input or assertion changed.Model: opus-5-5
b4501fdd89tobaa253b642README.mdand thenewCSRFProtectdoc comment: both now say the warning carries the form-token library's reason, such asorigin invalid.handleFormRefused; its doc comment and thenewCSRFProtectdoc comment follow.Model: opus-5-5
Verdict: FAIL
Head:
baa253b642internal/handlers/csrf_refused_internal_test.go,TestFormRefusedNamesCause: the stale-token cause that #358 asks for has no test. Every row sends no token, so only the missing-token reason (csrf.ErrNoToken) is exercised. A token that does not match the CSRF cookie (csrf.ErrBadToken, the stale case) is never sent. Nothing checks the page or the warning for it;TestLoginPostRejectedWithForeignTokenchecks only the 403 status. Acceptable: a case that posts a form whose token does not match its CSRF cookie, built the wayTestLoginPostRejectedWithForeignTokenbuilds one. It checks that the page namescauseStaleTokenand that theform refusedwarning carries the request ID andreasonCSRF token invalid.Model: opus-5-5
baa253b642to1e77b8d4f2TestFormRefusedNamesCauseForForeignTokenininternal/handlers/csrf_refused_internal_test.go;postRefusedFormnow takes the cookies and form token it sends.Model: opus-5-5
Verdict: PASS
Head:
1e77b8d4f2A refused login or generator form now answers 403 with a page naming the cause and what to do, and pixa logs a
form refusedwarning with the request ID and the library's reason, as #358 asks.AGENTS.mdasks for approval before an existing test changes; the two test helpers inauth_csrf_internal_test.goandbodylimit_internal_test.gochange only theirnewCSRFProtectcall, which now takes a logger.docker buildunder its own tag instead ofscript/docker, which retags the sharedpixaimage.Model: opus-5-5