fix(audit-2026-06-11/H-1): request-body ceilings (Caddy proxy + email-inbound webhook)
- Caddy request_body { max_size 12MB } in both Caddyfiles — a hard
ceiling replacing Caddy's multi-GB default; 12MB clears the
orchestrator's 10 MiB user-route read. Validated with caddy validate.
- email-inbound router gets an explicit DefaultBodyLimit::max(1MB):
the public unauthenticated webhook previously rode Axum's 2MB
extractor default; tightened so a flood can't force large
allocation + JSON parse per request.
Admin / execute handlers keep Axum's 2MB extractor default (already
bounded); the user-route path keeps its explicit 10 MiB manual read.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:80 {
|
:80 {
|
||||||
|
# Audit 2026-06-11 (H-1) — hard request-body ceiling at the proxy.
|
||||||
|
# Without this, Caddy forwards bodies up to its multi-GB default to
|
||||||
|
# picloud. 12 MB sits just above the orchestrator's 10 MiB user-route
|
||||||
|
# read so legitimate large invokes pass; admin / email handlers are
|
||||||
|
# bounded far tighter by their own Axum extractor limits.
|
||||||
|
request_body {
|
||||||
|
max_size 12MB
|
||||||
|
}
|
||||||
|
|
||||||
# Baseline headers applied to every response.
|
# Baseline headers applied to every response.
|
||||||
header {
|
header {
|
||||||
X-Content-Type-Options "nosniff"
|
X-Content-Type-Options "nosniff"
|
||||||
|
|||||||
@@ -19,6 +19,15 @@
|
|||||||
{$PICLOUD_DOMAIN} {
|
{$PICLOUD_DOMAIN} {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
|
||||||
|
# Audit 2026-06-11 (H-1) — hard request-body ceiling at the proxy.
|
||||||
|
# Without this, Caddy forwards bodies up to its multi-GB default to
|
||||||
|
# picloud. 12 MB sits just above the orchestrator's 10 MiB user-route
|
||||||
|
# read so legitimate large invokes pass; admin / email handlers are
|
||||||
|
# bounded far tighter by their own Axum extractor limits.
|
||||||
|
request_body {
|
||||||
|
max_size 12MB
|
||||||
|
}
|
||||||
|
|
||||||
# Baseline headers on every response. HSTS is unconditional in prod.
|
# Baseline headers on every response. HSTS is unconditional in prod.
|
||||||
header {
|
header {
|
||||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||||
|
|||||||
@@ -169,12 +169,20 @@ pub struct EmailInboundState {
|
|||||||
pub bad_sig_limiter: Arc<BadSignatureLimiter>,
|
pub bad_sig_limiter: Arc<BadSignatureLimiter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Per-request body cap for the inbound webhook. Inbound email events
|
||||||
|
/// are small JSON envelopes (from/to/subject/body); 1 MiB is generous.
|
||||||
|
/// Audit 2026-06-11 (H-1) — this public, unauthenticated endpoint
|
||||||
|
/// otherwise rode Axum's 2 MB extractor default; pin it explicitly and
|
||||||
|
/// tighter so a flood can't force large allocations + JSON parses.
|
||||||
|
const INBOUND_BODY_LIMIT_BYTES: usize = 1024 * 1024;
|
||||||
|
|
||||||
pub fn email_inbound_router(state: EmailInboundState) -> Router {
|
pub fn email_inbound_router(state: EmailInboundState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/email-inbound/{app_id}/{trigger_id}",
|
"/email-inbound/{app_id}/{trigger_id}",
|
||||||
post(receive_inbound_email),
|
post(receive_inbound_email),
|
||||||
)
|
)
|
||||||
|
.layer(axum::extract::DefaultBodyLimit::max(INBOUND_BODY_LIMIT_BYTES))
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user