Files
mnote/bugs/07-ai/done/7-45-onlyoffice-live-bridge-session-isolation-v1.md
lix-2026 1882db7681 收口 MNote P0 P1 P2 审查尾项
- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
2026-06-01 09:29:12 +08:00

6.5 KiB
Raw Permalink Blame History

7-45 ONLYOFFICE live bridge session 隔离风险

状态

  • 状态:done
  • Owner07-ai / OnlyOffice live bridge / mnote-web
  • 发现时间:2026-05-31

现象

ONLYOFFICE live bridge session 当前是进程内全局状态。工具调用未显式传 onlyofficeSessionId / bridgeSessionId 时,会 fallback 到最近活跃 session;同一文档的 bridge session id 又由 docKey 派生,重复打开同文档或多用户/多 tab 打开不同 Office 文档时,存在命令落到错误 Office tab 或 token 覆盖的风险。

证据

  • rust/crates/mnote-web/src/routes/onlyoffice.rs 生成 bridgeSessionId = "mnote-oo-" + fileState.docKey
  • rust/crates/mnote-web/src/routes/onlyoffice_bridge.rsregister_sessionsession_id 覆盖 tokendocument_idasset_idlast_seen_millis
  • rust/crates/mnote-web/src/routes/onlyoffice_bridge.rscurrent_session_info() 直接取全局 last_seen_millis 最大值。
  • rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rsresolve_session_id() 在未传 session id 时 fallback 到 current_session_info()

影响

  • Page AI 或 Hermes tool 可能在多 Office tab 场景写错目标文档。
  • 同一文档重复打开可能互相覆盖 bridge token,使旧 tab 的插件长轮询或结果回传失效。
  • 多用户共享同一 mnote-web 进程时,全局最近 session 可能跨用户泄漏目标选择。

最小复现建议

  1. 打开两个不同 Office 文档,或同一文档两个 tab。
  2. 不传 onlyofficeSessionId 调用 mnote.onlyoffice.session.current 与一个写工具,例如 mnote.onlyoffice.document.insert_text
  3. 观察返回 session 是否只由最近活跃 tab 决定。
  4. 同一文档双 tab 复测 token 覆盖后,旧 tab commands/nextresults 是否返回 401 / 无结果。

修复建议

  • bridge session id 加入 browser tab 级随机后缀,不只使用 docKey
  • Page AI run payload 必须携带当前 resource tab 的 explicit onlyofficeSessionId;写工具禁止默认 fallback 写入。
  • current_session 只能作为只读诊断工具,不能作为写工具默认目标。
  • session state 至少按 actor/session/document/resource 维度过滤。

本轮进展

  • 2026-05-31
    • onlyoffice.rs 已把 bridgeSessionId 从仅基于 docKey 改为 docKey + bridgeSessionSalt,避免同文档多 tab 共用同一个 bridge session id。
    • onlyoffice_live.rs 已禁止读/写 action fallback 到全局最近 session;读写工具必须显式传 onlyofficeSessionId / bridgeSessionId
    • onlyoffice.rs 的本地 callback 已绑定 bridge sessionId + token,未认证本地写回返回 401 并保持原文件不变。
    • 已补 Rust 定点测试覆盖缺 explicit session 的读/写工具拒绝,以及未认证 local callback 拒绝。
    • 已补 scripts/task515-onlyoffice-live-scope-http-smoke.js 覆盖 HTTP 层缺 explicit session 返回 400。
    • 已补并通过 scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js,在真实浏览器中验证 A/B 两个 bridge session 的 token 校验、command queue 和 result 回收互不串台,错误 token 返回 401。
  • 2026-06-01
    • 已补并通过 scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js,在真实 ONLYOFFICE iframe / DocumentServer 下打开同一 docx 两个 tab 和另一个 docx,验证同文档双 tab 共享 docKey 但使用不同 bridgeSessionId,三个插件 session 均能注册并各自回收 selection.get command。
    • task518 同时覆盖显式传 B session 但 scope=A 时返回 403,以及 scope=B 授权 dry-run 返回 200,证明真实 iframe session 能被工具层按显式 session 和 resource scope 约束。
    • Page AI open editors snapshot / target package 已开始保留 Office onlyofficeSessionId / bridgeSessionId,服务端 agentTargetPackageaiAccessScope.allowedResourceIds 已能保留 Office asset、session 和 resource:onlyoffice:{documentId}:{assetId} 候选;新增 Rust 定点测试 hermes_client_run_body_preserves_onlyoffice_target_scope 覆盖该合同。
    • ONLYOFFICE iframe 在设置 __MNOTE_ONLYOFFICE_DEBUG__ 后会向父窗口发送 mnote:onlyoffice-bridge-readyresource tab runtime 收到后事件驱动刷新 open editors snapshotPage AI 发送前也会拒绝没有 onlyofficeSessionId 的 Office target,避免 bridge 未就绪时发起 run。
    • task518 已扩展并通过非 dry-run 写入落点验证:通过 mnote.onlyoffice.document.insert_text 向 Office B 写入唯一 marker,再通过真实 iframe bridge document.export 导出 A/B 内容,断言 B 包含 marker 且 A 不包含;截图保存到 tmp/task518-onlyoffice-real-iframe-session-scope-smoke/screenshots/office-a-after-write.pngoffice-b-after-write.png
    • 已补并通过 scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js:在真实文档页内打开 Office resource tab,等待 iframe bridge ready 后通过 Page AI target picker 选择 Office target,断言 editorTargettargetPackage 均冻结真实 onlyofficeSessionId / bridgeSessionId,并断言无 consoleErrors / networkFailures / httpErrors

当前 P0 安全阻断已覆盖“工具不能隐式读写全局最近 session”“callback 不能未认证写本地文件”“bridge session/token/queue 在浏览器 HTTP 层隔离”“真实 iframe 插件 session 显式授权边界”“Page AI run scope 不再被服务端压回当前页面 id”“Office bridge 未就绪时 Page AI 发送前拒绝”“真实 iframe 非 dry-run 写入不串台”和“真实 Page AI UI 从 Office iframe 目标取到 live bridgeSessionId 并冻结进 run payload”。

验收

  • Rust 单测覆盖页面生成同 docKey 双 session 随机 session id。
  • Rust 单测覆盖读/写工具缺 explicit session id 时拒绝。
  • Rust 单测覆盖本地 callback 缺 bridge session/token 时拒绝。
  • Browser smoke 覆盖两个 bridge session 的 token、command queue 与 result 隔离。
  • Browser smoke 覆盖两个真实 Office iframe tab 下插件注册、sessionId 隔离和 command 回收。
  • Rust 单测覆盖 Page AI Office target package / aiAccessScope 保留 session 与 resource scope。
  • 前端事件驱动刷新覆盖 ONLYOFFICE bridge ready 消息,发送前拒绝缺 session 的 Office target。
  • Browser smoke 覆盖两个真实 Office iframe tab 下非 dry-run 写入目标不串台。
  • Browser smoke 覆盖 Page AI target picker 从真实 Office iframe target 读取 live onlyofficeSessionId 并写入 run payload。