Skip to content

feat(api): make request body-size limit declarative in the route registry - #415

Merged
krisarmstrong merged 1 commit into
mainfrom
feat/route-maxbodybytes
Jun 9, 2026
Merged

feat(api): make request body-size limit declarative in the route registry#415
krisarmstrong merged 1 commit into
mainfrom
feat/route-maxbodybytes

Conversation

@krisarmstrong

Copy link
Copy Markdown
Collaborator

stem's capability registry already made HTTP methods authoritative
(methods + methodGate); the body-size cap was the one piece of route
policy still living outside it — every handler relied on decodeJSONStrict
passing maxRequestBodySize per call site, so a route that forgot to decode
strictly had no body cap. This brings stem to seed parity (ADR-0002): the
registry is the single authoritative source for body limits too.

  • Add maxBodyBytes to the route struct (0 => default maxRequestBodySize).
  • Add bodyLimited middleware, composed innermost in register() so r.Body
    is capped before the handler reads (order: rateLimit → auth → methodGate
    → bodyLimit → handler).
  • Expose maxBodyBytes in the /__capabilities manifest (routePolicyView).
  • Assert in TestRoutePolicyManifest that every route records a non-zero
    body limit, so no route can ship with an uncapped body.

Safe to default-cap: stem has no multipart/upload routes — every body
path already goes through decodeJSONStrict at maxRequestBodySize (1 MB),
so the registry cap matches what was enforced inline. The existing inline
decode caps remain as defense-in-depth.

…stry

stem's capability registry already made HTTP methods authoritative
(methods + methodGate); the body-size cap was the one piece of route
policy still living outside it — every handler relied on decodeJSONStrict
passing maxRequestBodySize per call site, so a route that forgot to decode
strictly had no body cap. This brings stem to seed parity (ADR-0002): the
registry is the single authoritative source for body limits too.

- Add maxBodyBytes to the route struct (0 => default maxRequestBodySize).
- Add bodyLimited middleware, composed innermost in register() so r.Body
  is capped before the handler reads (order: rateLimit → auth → methodGate
  → bodyLimit → handler).
- Expose maxBodyBytes in the /__capabilities manifest (routePolicyView).
- Assert in TestRoutePolicyManifest that every route records a non-zero
  body limit, so no route can ship with an uncapped body.

Safe to default-cap: stem has no multipart/upload routes — every body
path already goes through decodeJSONStrict at maxRequestBodySize (1 MB),
so the registry cap matches what was enforced inline. The existing inline
decode caps remain as defense-in-depth.
@krisarmstrong
krisarmstrong enabled auto-merge (squash) June 9, 2026 00:13
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

License Compliance Report

All dependencies pass license compliance checks

Go Dependencies

  • Unknown: 31 package(s)
  • MIT: 26 package(s)
  • BSD-3-Clause: 16 package(s)
  • Apache-2.0: 11 package(s)
  • BSD-2-Clause: 1 package(s)

npm Dependencies

See full report in workflow artifacts

Allowed Licenses: MIT, Apache-2.0, BSD-*, ISC, CC0-1.0, MPL-2.0
Forbidden: GPL, AGPL, SSPL (strong copyleft)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant