Non-multipart request bodies ignore ROBYN_MAX_PAYLOAD_SIZE and a stream error panics the worker
Author: BitWeaverDevCreated Jul 31, 2026Updated Jul 31, 2026
Bug Description
For any request whose body isn't multipart/form-data, Robyn buffers the incoming body by manually draining the raw actix web::Payload stream:
https://github.com/sparckles/Robyn/blob/main/src/types/request.rs#L162-L168
} else {
let mut body_local = BytesMut::new();
while let Some(chunk) = payload.next().await {
let chunk = chunk.expect("Failed to read chunk from payload");
body_local.extend_from_slice(&chunk);
}
body_local.freeze().to_vec()
};Two problems with this loop:
ROBYN_MAX_PAYLOAD_SIZEhas no effect here. The app registersweb::PayloadConfig::new(max_payload_size)as app data (src/server.rs:233), butPayloadConfigonly governs actix'sweb::Bytes/web::Json/web::Formextractors — it is never consulted by a rawweb::Payloadstream. Since this code readspayloaddirectly and appends every chunk to an unboundedBytesMutwith no size check, a client can send a body of any size (megabytes to gigabytes) regardless of the configured limit, giving Robyn no actual protection against memory-exhaustion via a large request body. This is the documented behavior at https://robyn.tech under theROBYN_MAX_PAYLOAD_SIZEenv var — the docs say it bounds "HTTP requests and WebSocket messages," but for regular (non-multipart) HTTP bodies it silently does nothing.chunk.expect(...)panics the worker task on any stream read error — e.g. the client disconnects mid-upload, or sends malformed chunked transfer-encoding. Instead of the request failing with a 4xx/5xx, the whole async task panics.
Steps to Reproduce
- Start any Robyn app with
ROBYN_MAX_PAYLOAD_SIZE=1000set. POSTa JSON body larger than 1000 bytes to any route (non-multipartContent-Type).- Observe the request is processed normally instead of being rejected — the configured limit is not enforced.
- Separately: open a connection, start streaming a chunked request body, and abort the connection mid-stream — the async task panics instead of the request erroring cleanly.
Expected vs Actual
- Expected: the body read should stop and return an error response once
ROBYN_MAX_PAYLOAD_SIZEbytes have been read, and a stream error should produce an error response, not a panic. - Actual: the body is buffered without any bound, and a stream error panics the task.
Suggested Fix
In the loop, track the accumulated length against the configured max (the same value already threaded through for multipart, see max_payload_size usage nearby) and break/return an error once exceeded; replace chunk.expect(...) with a match/? that maps a stream error to a proper HTTP error response instead of panicking.
Additional Info
Found via a broader codebase audit while working on #485 (response compression). Robyn version: main branch.
Source: sparckles/Robyn