Skip to content

fix(engineioxide): enforce max_payload on the websocket transport#762

Open
schulzfel wants to merge 1 commit into
Totodore:mainfrom
schulzfel:fix/ws-max-payload
Open

fix(engineioxide): enforce max_payload on the websocket transport#762
schulzfel wants to merge 1 commit into
Totodore:mainfrom
schulzfel:fix/ws-max-payload

Conversation

@schulzfel

Copy link
Copy Markdown

Problem

EngineIoConfig.max_payload is only enforced for the polling transport (HTTP request bodies). Websocket connections are initialized with tungstenite's default WebSocketConfig, which allows ~64 MiB messages / 16 MiB frames — so the configured payload ceiling is silently unenforced for websocket clients. A server operator who sets max_payload to bound inbound payloads is only actually protected on polling.

Fix

Apply the same ceiling to inbound websocket traffic in ws::on_init:

let ws_config = WebSocketConfig::default()
    .read_buffer_size(engine.config.ws_read_buffer_size)
    .max_message_size(Some(engine.config.max_payload as usize))
    .max_frame_size(Some(engine.config.max_payload as usize));

max_frame_size bounds each frame at parse time and max_message_size bounds reassembled fragmented messages, giving both transports identical enforcement. An oversized message now closes the connection with a capacity error (surfaced as DisconnectReason::TransportError) instead of being delivered to the handler.

The max_payload doc comment is updated to mention websocket.

Tests

New crates/engineioxide/tests/ws_max_payload.rs (runs with --features __test_harness like the other integration tests):

  • ws_message_within_max_payload_is_delivered — messages under the limit still reach the handler.
  • ws_message_exceeding_max_payload_closes_the_connection — an oversized message disconnects with TransportError and never reaches the handler.

The second test fails without the ws.rs change and passes with it.

Notes

Downstream we currently carry this as a vendored two-line patch on 0.17.1 to safely enable the websocket transport behind a strict payload budget; happy to adjust anything (naming, error surface, backport) to get payload parity landed upstream.

max_payload was only enforced for the polling transport (http request
bodies). Websocket connections rode tungstenite's defaults (~64 MiB
message / 16 MiB frame), so the configured ceiling was silently
unenforced for websocket clients.

Map the same ceiling onto inbound websocket frames (max_frame_size)
and reassembled messages (max_message_size), giving both transports
identical payload enforcement. An oversized message now closes the
connection with a transport error instead of being delivered.
@codspeed-hq

codspeed-hq Bot commented Jul 17, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 87 untouched benchmarks


Comparing schulzfel:fix/ws-max-payload (979b193) with main (46df8dd)

Open in CodSpeed

@Totodore Totodore left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR,
However I think we should provide two different options for those two params (frame_size and message_size). These don't have exactly the same implication than max_payload setting.

Also could you propagate this to the socketioxide builder (see https://docs.rs/socketioxide/latest/socketioxide/struct.SocketIoBuilder.html#method.ws_read_buffer_size).

Comment on lines +120 to +123
// Apply the configured `max_payload` ceiling to inbound websocket
// frames/messages, matching the polling transport. Without this,
// tungstenite's defaults (~64 MiB message / 16 MiB frame) apply and
// `max_payload` is silently unenforced for websocket clients.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Apply the configured `max_payload` ceiling to inbound websocket
// frames/messages, matching the polling transport. Without this,
// tungstenite's defaults (~64 MiB message / 16 MiB frame) apply and
// `max_payload` is silently unenforced for websocket clients.

Useless AI-comments

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.

2 participants