fix(server): bound request body size to prevent upload memory DoS #97

Merged
icub3d merged 3 commits from feature/80-body-size-limit into main 2026-07-09 03:05:56 +00:00
Owner

Summary

Fixes #80. Multipart uploads bypass axum's DefaultBodyLimit, and the handlers buffered each field via field.bytes() with the max_file_size check applied only after the whole field was in memory. With no RequestBodyLimitLayer configured, a single huge field — or many fields in one request (the attachment loop accumulates) — could stream unbounded bytes into memory.

Fix

Add a RequestBodyLimitLayer (tower-http limit feature) sized to max_file_size + 8 KiB framing overhead, applied to the whole router. This caps the entire request body at the HTTP layer, so no field can exceed what we are willing to buffer, and returns 413 Payload Too Large (immediately when Content-Length exceeds the cap). JSON/text endpoints stay bounded by axum's smaller DefaultBodyLimit.

Note: the cap bounds the whole request, so a single upload request is effectively bounded to one max-size file plus framing. This matches the issue's concern about the multi-field accumulation; a dedicated multi-file budget can be a follow-up if needed.

Tests

  • oversized_request_body_is_rejected: over-limit body → 413 (before auth/routing); a small body is not rejected.

Full server suite green (116 tests); clippy -D warnings clean.

Closes #80

## Summary Fixes #80. Multipart uploads bypass axum's `DefaultBodyLimit`, and the handlers buffered each field via `field.bytes()` with the `max_file_size` check applied only *after* the whole field was in memory. With no `RequestBodyLimitLayer` configured, a single huge field — or many fields in one request (the attachment loop accumulates) — could stream unbounded bytes into memory. ## Fix Add a `RequestBodyLimitLayer` (tower-http `limit` feature) sized to `max_file_size + 8 KiB` framing overhead, applied to the whole router. This caps the entire request body at the HTTP layer, so no field can exceed what we are willing to buffer, and returns `413 Payload Too Large` (immediately when `Content-Length` exceeds the cap). JSON/text endpoints stay bounded by axum's smaller `DefaultBodyLimit`. Note: the cap bounds the *whole* request, so a single upload request is effectively bounded to one max-size file plus framing. This matches the issue's concern about the multi-field accumulation; a dedicated multi-file budget can be a follow-up if needed. ## Tests - `oversized_request_body_is_rejected`: over-limit body → 413 (before auth/routing); a small body is not rejected. Full server suite green (116 tests); `clippy -D warnings` clean. Closes #80
fix(server): bound request body size to prevent upload memory DoS
Some checks failed
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 11s
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 18s
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 21s
CI / Client — test & lint (pull_request) Successful in 52s
Security Audit / Semgrep (SAST) (pull_request) Failing after 1m17s
CI / Server — test & lint (pull_request) Successful in 2m52s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Failing after 3m45s
f4012bf7c7
Multipart uploads bypass axum's DefaultBodyLimit and were buffered field
by field via field.bytes() with the max_file_size check applied only after
the full field was in memory. No RequestBodyLimitLayer was configured, so a
single large field or many fields could stream unbounded bytes into memory.

Add a RequestBodyLimitLayer sized to max_file_size + 8 KiB framing overhead
(enabling tower-http's "limit" feature). This caps the whole request at the
HTTP layer, so a field can never exceed what we're willing to buffer. JSON
and text endpoints remain bounded by axum's smaller DefaultBodyLimit.

Test: an over-limit body returns 413 before auth/routing; a small body is
not rejected.

Closes #80

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merge branch 'main' into feature/80-body-size-limit
Some checks failed
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 25s
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 31s
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 11s
CI / Client — test & lint (pull_request) Successful in 46s
Security Audit / Semgrep (SAST) (pull_request) Failing after 59s
CI / Server — test & lint (pull_request) Successful in 1m57s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Has been cancelled
884910a968
# Conflicts:
#	server/src/main.rs
Merge branch 'main' into feature/80-body-size-limit
Some checks failed
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 27s
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 12s
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 15s
CI / Client — test & lint (pull_request) Successful in 1m4s
Security Audit / Semgrep (SAST) (pull_request) Failing after 1m24s
CI / Server — test & lint (pull_request) Successful in 6m56s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Failing after 9m1s
fde99a0117
# Conflicts:
#	server/src/main.rs
icub3d merged commit e95fa022bc into main 2026-07-09 03:05:56 +00:00
icub3d deleted branch feature/80-body-size-limit 2026-07-09 03:05:57 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
icub3d/decentcom!97
No description provided.