Skip to content

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.