You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Keep remote-desktop receive threads alive and handshake off the accept thread
Malformed input or a bad WebSocket frame killed the host's receive thread
without stop(), leaving a handler that held a client slot, and the viewer's
thread died without telling the GUI. The TLS, WS and auth handshake ran on
the single accept thread, so one slow peer blocked every other viewer; each
connection now gets its own bounded handshake thread. Signaling timeouts and
hang-ups are raised as SignalingError.
Copy file name to clipboardExpand all lines: docs/updates/2026-09.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -398,3 +398,13 @@ Index and query commands: [README.md](README.md). New entries go at the end.
398
398
-**Tests**: `test_codegen_injection.py` (new, 4; all fail on the previous commit), `test_network_edge_audit.py` (new, 6; it does not import on the previous commit, `atomic_write_bytes` being new). `test_codegen.py`, `test_usbip.py` and `test_r3_rdusb_usbip.py` updated as described.
-**What**: The robustness findings of the remote-desktop network audit (the security ones are U-20260923-29).
405
+
-**Receive threads**: (1) an INPUT message with a missing field (`KeyError`), an infinite or non-numeric coordinate (`OverflowError` / `ValueError` from `int()`), or an unknown key or button name (the wrappers' `AutoControlException`) escaped the host's catch; the receive thread died without `stop()`, so the handler still counted as an authenticated client, ignored all later input and — with `max_clients=1` — locked out every new viewer. `dispatch_input` turns conversion errors into `InputDispatchError`, the host catches the wrappers' framework errors, and `_recv_loop` calls `stop()` before letting anything else propagate. (2) `WsProtocolError` was a bare `RuntimeError`, so one TEXT or fragmented frame killed the WebSocket host's receive thread the same way and the viewer's silently (`on_error` never ran); it is a `ProtocolError`, which both loops already end a connection on.
406
+
-**Accept thread**: the TLS handshake, the WS upgrade and authentication ran on the one accept thread, each read bounded by a 60 s *per-read* timeout, so a peer sending a byte just inside each timeout kept everyone else from connecting (8.6 s and counting in the reproduction). Each connection is handshaken on its own `rd-handshake` thread, at most 32 at once; beyond that new connections are closed.
407
+
-**Signaling client**: a read timeout (`TimeoutError`) or a server hanging up (`RemoteDisconnected`) is no `URLError`, so they left `_request` as themselves while the GUI workers catch only `SignalingError`; they are wrapped too.
408
+
-**Tests**: `test_rd_robustness_audit.py` (new, 8; all fail on the previous commit). The 360 existing remote-desktop tests pass unchanged, including the two that pin the stop-during-handshake window.
0 commit comments