Skip to content

environmentd: make webhook request size limit configurable via dyncfg#37252

Closed
sjwiesman wants to merge 1 commit into
MaterializeInc:mainfrom
sjwiesman:webhook-configurable-request-size
Closed

environmentd: make webhook request size limit configurable via dyncfg#37252
sjwiesman wants to merge 1 commit into
MaterializeInc:mainfrom
sjwiesman:webhook-configurable-request-size

Conversation

@sjwiesman

Copy link
Copy Markdown
Contributor

Motivation

Webhook source requests are hardcoded to a 5 MiB body size limit, enforced globally for all environmentd HTTP routes by an Axum DefaultBodyLimit layer (src/environmentd/src/http.rs). Operators have no way to raise it for webhook sources that legitimately need to accept larger payloads.

What this does

Introduces the webhook_max_request_size dyncfg (default 5 MiB) that bounds the size of a webhook request body, read live on every request. Other HTTP routes (SQL API, WebSocket, etc.) keep the existing 5 MiB MAX_REQUEST_SIZE. Oversized requests are rejected with 413 Payload Too Large.

Reading the limit cheaply

The webhook handler is deliberately coordinator-free in steady state (it caches the appender via WebhookAppenderCache), so reading the limit must not add a coordinator round-trip. Rather than get_system_vars().await (which sends a command to the coordinator and awaits a reply), the handler reads a clone of the process-wide live ConfigSet carried on the shared PersistClientCache. Cloning a ConfigSet shares its inner atomics, so reads are O(1) atomic loads that reflect runtime updates. The storage controller already applies the full set of dyncfg updates to that ConfigSet via apply_from, and catalog_config.persist_clients / controller.persist_clients are the same Arc<PersistClientCache>, so updates are visible to the webhook's clone.

Enforcement

The handler's body extractor switches from Bytes (which silently inherits the global DefaultBodyLimit) to axum::body::Body, collected via to_bytes(body, limit). Because the handler runs inside the request-decompression layer, the limit applies to the decompressed body, preserving today's decompression-bomb protection. A from_fn middleware would have been forced outside decompression by Axum's body-type plumbing (limiting only the compressed wire size), which is why enforcement lives in the handler.

Testing

Adds a case to test/testdrive/webhook.td that shrinks the limit and confirms a larger body is rejected with 413, then raises the limit and confirms the same body is accepted without a restart — proving the dyncfg is read live per request.

Release note

This release will allow the maximum webhook request body size to be configured at runtime via the webhook_max_request_size system parameter.

🤖 Generated with Claude Code

Webhook source requests were hardcoded to a 5 MiB body size limit,
enforced globally for all environmentd HTTP routes by an Axum
`DefaultBodyLimit` layer. Operators had no way to raise it for webhook
sources that legitimately accept larger payloads.

This introduces the `webhook_max_request_size` dyncfg (default 5 MiB)
that bounds the size of a webhook request body, read live on every
request. Other HTTP routes keep the existing 5 MiB `MAX_REQUEST_SIZE`.

The webhook handler is deliberately coordinator-free in steady state
(it caches the appender), so reading the limit must not add a
coordinator round-trip. Instead of `get_system_vars().await`, the
handler reads a clone of the process-wide live `ConfigSet` carried on
the shared `PersistClientCache`. Cloning a `ConfigSet` shares the inner
atomics, so reads are O(1) atomic loads that reflect runtime updates;
the storage controller already applies the full dyncfg updates to that
set via `apply_from`.

Enforcement moves into the handler: the body extractor switches from
`Bytes` (which silently inherits the global `DefaultBodyLimit`) to
`axum::body::Body`, collected via `to_bytes(body, limit)`. Because the
handler runs inside the request-decompression layer, the limit applies
to the decompressed body, preserving today's decompression-bomb
protection. Oversized requests are rejected with `413 Payload Too
Large`.

Adds a testdrive case to test/testdrive/webhook.td that shrinks the
limit, confirms a larger body is rejected with 413, then raises the
limit and confirms the same body is accepted without a restart.

This release will allow the maximum webhook request body size to be
configured at runtime via the `webhook_max_request_size` system
parameter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZotyjwhUpWkDViMF5hwjS
@sjwiesman sjwiesman closed this Jun 23, 2026
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