fix(room): guard against non-dict WS payloads and unhashable answers
The first-pass JSON-decode hardening exposed two latent bugs that the
fuzz scenario hits as soon as the WS handler stays alive past a bad
message:
1) `data.get("type")` is called on whatever `receive_json()` decodes,
but valid JSON can be a list/string/number, not just a dict. Reject
non-object payloads with a structured bad_message error before
dispatch.
2) `submit_answer` did `if answer not in {"A","B","C","D"}` which
raises TypeError when the client sends an unhashable answer
(e.g. a dict). Add an isinstance(str) guard so any non-string
answer falls into the bad_answer branch instead of crashing the
handler.
31/31 pytest still passes. Together with the prior commit, the WS
handlers now survive the full set of fuzz payloads without dropping
the connection.
This commit is contained in:
14
app/room.py
14
app/room.py
@@ -92,6 +92,12 @@ class RoomManager:
|
|||||||
except (WebSocketDisconnect, RuntimeError):
|
except (WebSocketDisconnect, RuntimeError):
|
||||||
break
|
break
|
||||||
continue
|
continue
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
try:
|
||||||
|
await websocket.send_json({"type": "error", "code": "bad_message", "message": "Message must be a JSON object"})
|
||||||
|
except (WebSocketDisconnect, RuntimeError):
|
||||||
|
break
|
||||||
|
continue
|
||||||
msg_type = data.get("type")
|
msg_type = data.get("type")
|
||||||
if msg_type == "ping":
|
if msg_type == "ping":
|
||||||
await websocket.send_json({"type": "pong"})
|
await websocket.send_json({"type": "pong"})
|
||||||
@@ -119,6 +125,12 @@ class RoomManager:
|
|||||||
except (WebSocketDisconnect, RuntimeError):
|
except (WebSocketDisconnect, RuntimeError):
|
||||||
break
|
break
|
||||||
continue
|
continue
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
try:
|
||||||
|
await websocket.send_json({"type": "error", "code": "bad_message", "message": "Message must be a JSON object"})
|
||||||
|
except (WebSocketDisconnect, RuntimeError):
|
||||||
|
break
|
||||||
|
continue
|
||||||
msg_type = data.get("type")
|
msg_type = data.get("type")
|
||||||
if msg_type == "ping":
|
if msg_type == "ping":
|
||||||
await websocket.send_json({"type": "pong"})
|
await websocket.send_json({"type": "pong"})
|
||||||
@@ -268,7 +280,7 @@ class RoomManager:
|
|||||||
qidx = int(question_idx)
|
qidx = int(question_idx)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return {"type": "error", "code": "bad_question", "message": "Invalid question index"}
|
return {"type": "error", "code": "bad_question", "message": "Invalid question index"}
|
||||||
if answer not in {"A", "B", "C", "D"}:
|
if not isinstance(answer, str) or answer not in {"A", "B", "C", "D"}:
|
||||||
return {"type": "error", "code": "bad_answer", "message": "Answer must be A, B, C, or D"}
|
return {"type": "error", "code": "bad_answer", "message": "Answer must be A, B, C, or D"}
|
||||||
async with self.locks[sid]:
|
async with self.locks[sid]:
|
||||||
session = await self.get_session(sid)
|
session = await self.get_session(sid)
|
||||||
|
|||||||
Reference in New Issue
Block a user