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
DataStreamHandler.on_message in buckaroo/server/websocket_handler.py:38 does msg = json.loads(message) then immediately msg.get("type") without checking msg is a dict. JSON shapes other than a dict (null, [1,2,3], scalars) hit AttributeError on the .get call. Tornado swallows the exception and closes the underlying stream; the WS is dead for the rest of the connection.
Adjacent: unknown type values (type: 42, missing type, empty {}) are silently dropped — no response, no log, client times out waiting.
Findings from WS fuzz (smorg/post-785-playground)
input
result
"not json"
✓ structured {"type": "error", "error_code": "invalid_json"} returned
null
💥 WS closed; subsequent reads fail
[1, 2, 3]
⚠ silently dropped (AttributeError, but stream survived this time)
{}
⏱ silently dropped (no branch matches, return)
{"type": 42}
⏱ silently dropped
{"type": {"a": 1}}
⏱ silently dropped
{"payload": "x"} (no type)
⏱ silently dropped
{"type": "buckaroo_invented_command"}
⏱ silently dropped
The state_change handler does handle malformed new_state shapes (returns {"type": "error", "error_code": "state_change_error"}) — but the prior layer (on_message itself) lets bare types through to that handler unchecked.
Why it matters
bare_null: a single ws.send("null") from any client (or a buggy serialiser that produces null for None) kills the entire WS session permanently. Easy hostile DoS — close the WS by sending one literal null.
Failing-test commit: a WS test that sends each of the 8 malformed shapes above and asserts the server returns a structured error frame (not a crash, not a silent drop, not a hang).
Fix commit: the guards above.
Regression: existing test_load_expr.py tests pass; the WS-level happy paths are unchanged.
Found via
WS protocol fuzz suite on smorg/post-785-playground stress run.
Summary
DataStreamHandler.on_messageinbuckaroo/server/websocket_handler.py:38doesmsg = json.loads(message)then immediatelymsg.get("type")without checkingmsgis a dict. JSON shapes other than a dict (null,[1,2,3], scalars) hitAttributeErroron the.getcall. Tornado swallows the exception and closes the underlying stream; the WS is dead for the rest of the connection.Adjacent: unknown
typevalues (type: 42, missingtype, empty{}) are silently dropped — no response, no log, client times out waiting.Findings from WS fuzz (smorg/post-785-playground)
"not json"{"type": "error", "error_code": "invalid_json"}returnednull[1, 2, 3]{}{"type": 42}{"type": {"a": 1}}{"payload": "x"}(notype){"type": "buckaroo_invented_command"}The state_change handler does handle malformed
new_stateshapes (returns{"type": "error", "error_code": "state_change_error"}) — but the prior layer (on_messageitself) lets bare types through to that handler unchecked.Why it matters
ws.send("null")from any client (or a buggy serialiser that produces null for None) kills the entire WS session permanently. Easy hostile DoS — close the WS by sending one literalnull.Suggested fix
A small guard at the top of
on_message:Test plan
test_load_expr.pytests pass; the WS-level happy paths are unchanged.Found via
WS protocol fuzz suite on smorg/post-785-playground stress run.
🤖 Generated with Claude Code