Pin the body cap's order in every page route group (closes #93)
check / check (push) Successful in 3m28s

A route test now posts an oversized body with no session or CSRF
token to a POST route in each page route group that has one, and
requires 413 with no CSRF cookie. Before, only the login form pinned
the cap ahead of CSRF; reordering the /hooks or /hook groups failed
nothing.

The MaxBodySize doc comment says other methods pass uncapped on
purpose, the middleware test comment names the helper it describes,
and NewRouterForTest says why its hand-built Server is enough. The
README already described the cap's position correctly.

Model: opus-5-5
This commit is contained in:
2026-10-02 14:54:59 +00:00
parent 0ccb01cada
commit f76a175091
4 ha cambiato i file con 55 aggiunte e 5 eliminazioni
+4 -1
Vedi File
@@ -600,7 +600,10 @@ func bodyLimitedMethod(method string) bool {
}
// MaxBodySize returns middleware that limits the size of
// POST/PUT/PATCH request bodies to maxBytes. It must be registered
// POST/PUT/PATCH request bodies to maxBytes. A request with any other
// method passes through uncapped, deliberately: no route behind it
// reads a body on GET, HEAD or DELETE. A handler that starts to needs
// its method added to bodyLimitedMethod first. It must be registered
// before any middleware that parses the body — notably CSRF, which
// calls r.PostFormValue — so that form parsing happens under this
// cap rather than net/http's 10 MB default.
+6 -4
Vedi File
@@ -730,10 +730,8 @@ func TestNoCache_SetsHeaders(t *testing.T) {
const testBodyLimit int64 = 64
// maxBodySizeHandler wraps a sentinel handler in MaxBodySize with
// testBodyLimit. The sentinel records whether it ran and how much of
// the body it managed to read, so tests can distinguish "never
// reached" from "reached but truncated".
// maxBodySizeResult is what runMaxBodySize's sentinel handler saw,
// together with the response.
type maxBodySizeResult struct {
called bool
read int
@@ -741,6 +739,10 @@ type maxBodySizeResult struct {
response *httptest.ResponseRecorder
}
// runMaxBodySize wraps a sentinel handler in MaxBodySize with
// testBodyLimit and serves req through it. The sentinel records
// whether it ran and how much of the body it managed to read, so
// tests can distinguish "never reached" from "reached but truncated".
func runMaxBodySize(
t *testing.T,
req *http.Request,
+7
Vedi File
@@ -39,6 +39,13 @@ func SentryClientOptionsForTest(
// and the HTTP listener. Tests use it so that route-group middleware
// registration order is exercised exactly as it ships, rather than
// against a hand-rebuilt chain that could drift from routes.go.
//
// The Server itself is built by hand rather than through New, because
// New registers the fx hooks that start the listener. That is enough
// only while SetupRoutes reads no fields beyond mw, h, params.Config
// and sentryEnabled: a field it starts reading must also be set here
// and in the two probe variants below, or these tests run with it
// zero.
func NewRouterForTest(
log *slog.Logger,
cfg *config.Config,
+38
Vedi File
@@ -531,6 +531,44 @@ func TestStaticServesOnlyGetAndHead(t *testing.T) {
}
}
// --- every page route group ---
// TestPageRouteGroups_OversizeBody_RejectedBeforeCSRF pins the body
// cap ahead of CSRF and RequireAuth in every page route group that
// has a POST route. The requests carry no session and no CSRF token,
// so if either ran first the answer would be a 403 or a redirect to
// the login page rather than 413, and CSRF would issue its cookie
// (see TestPagesLogin_UnderLimit_NoToken_CSRFRejects). The user and
// webhook in the paths need not exist: nothing after the cap runs.
func TestPageRouteGroups_OversizeBody_RejectedBeforeCSRF(
t *testing.T,
) {
t.Parallel()
env := newTestEnv(t)
form := url.Values{}
form.Set("name", oversizeValue())
for _, path := range []string{
"/pages/login",
"/user/nobody/password",
"/hooks/new",
"/hook/nonexistent/edit",
} {
w := env.post(path, form, nil)
assert.Equal(
t, http.StatusRequestEntityTooLarge, w.Code, path,
)
assert.False(
t, csrfCookieSet(w),
"CSRF middleware must not run for an oversized body to %s",
path,
)
}
}
// --- /pages group ---
// TestPagesLogin_OversizeBody_RejectedBeforeCSRF proves the cap runs