Skip to content

fix: 安全与运行时正确性修复(H1/H2 + M2/M4/M5/L4/L5)#10

Open
lgy1027 wants to merge 3 commits into
mainfrom
fix/runtime-scheduling-and-stream-correctness
Open

fix: 安全与运行时正确性修复(H1/H2 + M2/M4/M5/L4/L5)#10
lgy1027 wants to merge 3 commits into
mainfrom
fix/runtime-scheduling-and-stream-correctness

Conversation

@lgy1027

@lgy1027 lgy1027 commented Jul 19, 2026

Copy link
Copy Markdown
Owner

概述

合并两条修复分支,覆盖代码审核发现的安全(High)与运行时正确性(Medium/Low)问题。

安全修复(High)

H1 — LLM DNS pinning TOCTOU(app/services/llm_gateway.py)

__init__ 缓存校验过的 IP,请求时 pin 到缓存 IP 而非每次重新解析;新增 _assert_no_dns_rebind 在请求前再校验一次当前 DNS 与缓存一致,不一致拒绝调用并降级到 extractive。防 init→request 之间 DNS rebinding 重定向到公网。allow_public 模式跟随 DNS 轮询,不缓存不校验。

H2 — WS testclient 后门 + 威胁模型文档化(realtime_auth.py / security.py)

删除 WS loopback 集合里的 testclient(Starlette TestClient 固定 host,为测试开后门)。is_trusted_browser_origin 注释说明无 Origin 放行是有意为之(浏览器同源 fetch 在部分实现如 Firefox 下不携带 Origin,强制要求会打断 SPA);CSRF 由不可信 Origin 拒绝防护;本机隔离走 DEPLOYMENT_MODE=lan / LOCAL_AUTH_DISABLED=false

运行时正确性(Medium/Low)

M2 — InferenceCoordinator 不饿死 offline(app/runtime.py)

offline 加饿死超时(30s),超时后放宽进入条件为 not active + 置 _offline_starving 让 live 谦让一次。连续会议接力(live_waiters 恒 >0)不再无限期饿死 refinement job。用单层 wait_for 超时避开嵌套锁陷阱。

M4 — 超时不重置 last_full_text(app/api/websocket.py)

超时只是"队列暂空"非"语音段结束",重置基准会让下一段含旧前缀的 ASR 文本被当增量重发 → 前端重复字。静音触发的 reset 语义不变。

M5 — ASR 加载超时清理 daemon 线程(engine/asr_engine.py)

超时放弃设备后置 cancelled flag,daemon worker 在 snapshot_download/from_pretrained 阶段边界主动 return,减少与下一设备加载线程并发占显存/内存。

L4 — 声纹 ID 格式统一(三引擎)

compare_and_identifyembedding=None 时返回 Spk_unknown 而非 Unknown,符合 ^Spk_ 格式,与下游默认值及 speaker_id 校验对齐。

L5 — WebSocket 连接速率限制(app/services/realtime_auth.py)

进程级 per-IP 滑动窗口(20/60s,超限 close 4401),补 HTTP 限流覆盖不到的 WS 路径,防空打 WS 占 fd/协程。conftest 加 autouse fixture 清理计数器防跨测试污染。

测试

  • 全部 450 测试通过(新增 11 个回归测试)
  • H1: DNS rebind 拒绝、allow_public 不校验
  • H2: 可信/无 Origin 放行、testclient 不再放行 WS
  • M2: offline 饿死调度
  • M4: 超时不重置 last_full_text(已验证能抓回归)
  • L4: 三引擎 Spk_unknown 格式
  • L5: WS 限流阈值 / per-IP 独立

说明

  • H2 的"无 Origin 绕过"未照搬审核建议改为"必须有 Origin",因为本机攻击者可任意伪造 Origin,强制要求是表演性防御却会打断 Firefox 同源 fetch 的 SPA。真正 CSRF 防护(不可信 Origin 拒绝)已实现;本机隔离的逃生口是 DEPLOYMENT_MODE=lan / LOCAL_AUTH_DISABLED=false
  • M2 测试验证"饿死超时后 offline 能进入 + starving 标志置位"(核心保证),live 谦让是加固层。

lgy1027 added 3 commits July 19, 2026 12:33
H1: LLMGateway init 时缓存校验过的 IP,请求时 pin 到缓存 IP
而非每次重新解析;新增 _assert_no_dns_rebind 在请求前再校验
一次当前 DNS 与缓存一致,不一致拒绝调用并降级到 extractive,
防 init→request 之间 DNS rebinding 重定向到公网。
allow_public 模式跟随 DNS 轮询,不缓存不校验。

H2: 删除 realtime_auth.py WS loopback 集合里的 'testclient'
(Starlette TestClient 固定 host,属为测试开后门);在
is_trusted_browser_origin 补注释说明无 Origin 放行是有意为之
(浏览器同源 fetch 部分实现不携带 Origin),CSRF 由不可信 Origin
拒绝防护,本机隔离走 DEPLOYMENT_MODE=lan / LOCAL_AUTH_DISABLED=false。

补充 6 个回归测试:DNS rebind 拒绝、allow_public 不校验、
可信 Origin 放行、无 Origin 放行、testclient 不再放行 WS。
M2: InferenceCoordinator 给 offline 加饿死超时(30s),超时后放宽
进入条件为 not active 并置 _offline_starving 让 live 谦让一次,保证
连续会议接力(live_waiters 恒>0)不饿死 refinement job。用单层
wait_for 超时避免 wait_for 嵌套的锁陷阱。

M4: audio_processor 超时分支(队列暂空)不再重置 ctx.last_full_text。
超时只是队列暂空非语音段结束,重置基准会让下一段含旧前缀的 ASR 文本
被当增量重发 → 前端重复字。静音触发的 reset 语义不变。

M5: ASR _load_with_fallback 超时放弃设备后置 cancelled flag,daemon
worker 在阶段边界(snapshot_download / from_pretrained 之间)主动
return,减少与下一设备加载线程并发占显存/内存。

L4: 三引擎 compare_and_identify 在 embedding=None 时返回 'Spk_unknown'
而非 'Unknown',符合 ^Spk_ 格式,与下游默认值及 speaker_id 校验一致。

L5: realtime_auth 加进程级 WS 连接速率限制(per-IP 20/60s,超限
close 4401),补 HTTP 限流覆盖不到的 WS 路径,防空打 WS 占 fd/协程。
conftest 加 autouse fixture 清理限流计数器防跨测试污染。

补充测试:offline 饿死调度、超时不重置 last_full_text、三引擎 Spk_unknown
格式、WS 限流阈值/per-IP 独立。
…cheduling-and-stream-correctness

# Conflicts:
#	app/services/realtime_auth.py
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.

1 participant