SCRUM-195 — gateway_dev: request body caps and the pack-upload route¶
Plan ref: GWD-3 (docs/11-admin-plane-plan.md). Stacked on SCRUM-194. Closes the
body-size half of gap 12 in doc 10.
What changed¶
| Change | Where |
|---|---|
Route fields MaxBody int64 (0 = DefaultMaxBody, 1 MiB) and Upload bool |
internal/router/route.go |
New literal route POST /api/admin/config/packs → config, live_ops, MaxBody 512 MiB, Upload (more specific than the viewer config prefix) |
internal/router/dev.go |
Body cap: Content-Length over the cap → 413 before proxying; otherwise MaxBytesReader (covers chunked bodies); an overflow mid-copy maps to 413 in the proxy's ErrorHandler instead of 502 |
internal/proxy/proxy.go |
Upload: clears both read and write deadlines (net/http's WriteTimeout runs from the end of the request headers, so a long upload would die on it too) |
same |
413 body: {"error":{"code":"body_too_large","message":"request body exceeds 1048576 bytes",…}}.
Deadlines are cleared only after auth has admitted the request, so only an
authenticated live_ops token can hold a slow upload open.
Downstream note (SCRUM-217): Config's own server has CONFIG_READ_TIMEOUT
(10s default); its pack handler must clear its own deadlines the same way, or a
large upload will pass the gateway and die at Config.
How to verify¶
cd services/gateway_dev
go vet ./... && go test -race -count=1 ./...
go test -count=1 -v -run 'BodyCap|UploadRoute|PackUpload|RoutePolicy' ./...
bash testdata/smoke/smoke.sh | grep -c FINDING # 0
| Test | Proves |
|---|---|
TestBodyCap_ContentLengthOverLimitIs413 |
2 MiB with Content-Length → 413, upstream never called |
TestBodyCap_ChunkedOverLimitIs413 |
2 MiB chunked (no length) → 413 |
TestBodyCap_UnderLimitPasses |
1 MiB − 1 passes |
TestBodyCap_UploadRouteAllowsLargeBody |
3 MiB on the packs route reaches upstream intact |
TestUploadRouteSurvivesSlowBody |
read/write timeouts 1s, body stalls 3s (synctest): upload route → 200 with every byte; same stall on a normal route is cut off (control case) |
TestPackUploadRouteShape |
only the packs route has Upload/MaxBody; it wins over the config prefix at live_ops |
TestRoutePolicyUnderRealMiddleware (+2 rows) |
packs: viewer 403, live_ops 200 → config |
By hand through the gateway (staff token $T):
head -c 2097152 /dev/zero | curl -s -X POST --data-binary @- -H "Authorization: Bearer $T" \
localhost:8090/api/admin/session/x # 413 body_too_large
Results at time of writing¶
go vet,go test -race: pass (4 packages). Smoke: 0 findings.
How it was built¶
DeepSeek run scoped (Landlock) to services/gateway_dev (186 s, ~25k output
tokens). Claude review: proxy logic and test control case read; Config-side
timeout dependency found and recorded above.