Skip to content

fix(dashboard): cap the contract envelope before decoding it - #111

Closed
juicycleff wants to merge 1 commit into
mainfrom
fix/dashboard-contract-body-limit
Closed

juicycleff wants to merge 1 commit into
mainfrom
fix/dashboard-contract-body-limit

Conversation

@juicycleff

Copy link
Copy Markdown
Contributor

The contract transport now caps the request envelope at 1 MiB. Anything larger gets a 413 with BAD_REQUEST, and the dispatcher never sees it.

If you need a different limit:

  • transport.WithMaxBodyBytes(n) on NewHandler or NewHandlerWithCSRF
  • server.WithMaxBodyBytes(n) for remote contributors built with server.New
  • contract_max_body_bytes in the dashboard config, or WithContractMaxBodyBytes(n)

Zero or less keeps the default. There's no setting that turns the cap off.

Why

The handler decoded the whole envelope before it evaluated the intent's Requires predicate, and it put no limit on the body. Any principal, a read-only one included, could post an arbitrarily large body to any intent and the server would read and decode all of it before refusing. We ran into this reviewing chronicle's dashboard contract, where reports.generateCustom accepts user-supplied text.

Moving the Requires check ahead of the decode isn't possible, because the intent name only exists inside the body (the URL carries the envelope version and nothing else). So the cap is what bounds the work. If the declared Content-Length is over the limit, the handler refuses without reading anything. For a chunked body, http.MaxBytesReader stops one byte past the limit.

Tests

http_limit_test.go covers a 4 MiB chunked body (413, dispatcher not called, at most limit + 1 bytes read off the wire), an over-limit declared length (413, zero bytes read), a body exactly at the default limit (200), and a configured 4096-byte limit at 4096 and 4097 bytes. A server test checks that server.WithMaxBodyBytes reaches the dispatch handler.

We ran the new tests first against the option with enforcement left out. Every over-limit case came back 200 and was dispatched. With the fix, go test ./extensions/dashboard/... passes.

The contract transport now refuses a request body over 1 MiB with 413
and BAD_REQUEST. You can change the cap with
transport.WithMaxBodyBytes, server.WithMaxBodyBytes, or the dashboard's
contract_max_body_bytes setting. Zero or less keeps the default, so
there's no way to turn it off.

Before this, the handler read and decoded the whole envelope before it
looked at the intent's Requires predicate, and it had no limit at all.
Any principal, read-only ones included, could post an arbitrarily large
body to any intent and the server would work through all of it before
saying no. We found this while reviewing chronicle's
reports.generateCustom, which takes user-supplied text.

The Requires check still runs after the decode, because the intent name
only exists inside the body. If the declared Content-Length is over the
cap, the handler refuses without reading anything. For a chunked body,
http.MaxBytesReader stops one byte past the cap.
@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
forge Ready Ready Preview Sep 29, 2026 9:41pm UTC

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Conventional Commits Validation

PR Title: valid
Commits: all 1 follow conventional format

@juicycleff

Copy link
Copy Markdown
Contributor Author

Landed on main as 4cee987.

@juicycleff juicycleff closed this Sep 30, 2026
@juicycleff
juicycleff deleted the fix/dashboard-contract-body-limit branch September 30, 2026 13:25

This branch was successfully deployed

1 active deployment
Preview — 51974e52 Deployed Sep 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant