#1425·Robyn

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

rust
} 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:

  1. ROBYN_MAX_PAYLOAD_SIZE has no effect here. The app registers web::PayloadConfig::new(max_payload_size) as app data (src/server.rs:233), but PayloadConfig only governs actix's web::Bytes/web::Json/web::Form extractors — it is never consulted by a raw web::Payload stream. Since this code reads payload directly and appends every chunk to an unbounded BytesMut with 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 the ROBYN_MAX_PAYLOAD_SIZE env var — the docs say it bounds "HTTP requests and WebSocket messages," but for regular (non-multipart) HTTP bodies it silently does nothing.
  2. 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

  1. Start any Robyn app with ROBYN_MAX_PAYLOAD_SIZE=1000 set.
  2. POST a JSON body larger than 1000 bytes to any route (non-multipart Content-Type).
  3. Observe the request is processed normally instead of being rejected — the configured limit is not enforced.
  4. 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_SIZE bytes 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.