security: 白板 WS 加单帧大小限制与单 board 连接数上限

安全审计发现:
- WS receive_text 无应用层大小限制,恶意客户端可发超大帧(uvicorn 默认 16MB
  才拦截)占内存。加 512KB 单帧检查(与 content 256KB 限制对齐留余量)。
- 单 board 无连接数上限,恶意脚本可开海量连接耗尽资源。config 加
  max_connections_per_board(默认 50),hub.register 超限返回 False,
  controller 回 error 帧并关闭连接。

审计结论(无需修复):
- SQL:全部 SQLAlchemy ORM 参数化,无注入。
- 路径穿越:storage_path 服务端生成(uuid+basename(ext)),用户不可控分隔符。
- 鉴权:管理类操作均有 require_docs_auth,公开写接口符合设计。
- 下载 filename CRLF:Starlette FileResponse 用 quote() 编码,无响应拆分。
- XSS:前端 el() 用 createTextNode,textarea 纯文本,innerHTML 仅用于静态文案。
This commit is contained in:
zikai
2026-07-21 15:09:13 +00:00
parent 374c3d150f
commit da7d2fb4b4
3 changed files with 21 additions and 3 deletions

View File

@@ -124,7 +124,11 @@ async def whiteboard_ws(websocket: WebSocket, board_id: str) -> None:
# 注册连接并下发 init
conn = Connection(websocket=websocket, board_id=board_id, client_id=client_id)
await hub.register(conn)
ok = await hub.register(conn)
if not ok:
await _safe_send(websocket, {"type": "error", "msg": "该记事本在线人数已满"})
await _safe_close(websocket)
return
await _safe_send(websocket, {
"type": "init",
"content": board.content,
@@ -133,9 +137,14 @@ async def whiteboard_ws(websocket: WebSocket, board_id: str) -> None:
})
# 主循环:收消息 -> 处理 -> 广播
# 单帧大小上限:与 content 限制对齐256KB 文本 + JSON 开销,留余量到 512KB
MAX_FRAME = 512 * 1024
try:
while True:
raw = await websocket.receive_text()
if len(raw) > MAX_FRAME:
await _safe_send(websocket, {"type": "error", "msg": "消息过大"})
continue
msg = _parse(raw)
if msg is None:
continue