Files
mnote/bugs/07-ai/done/7-46-onlyoffice-live-tool-resource-scope-bypass-v1.md
T
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.2 KiB
Raw Blame History

7-46 ONLYOFFICE live tool 缺少 resource scope 绑定

状态

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

现象

ONLYOFFICE live 写工具当前主要检查 idempotencyKeydryRunaiAccessScope.permissionLevel 和 command context 写权限,但没有把 bridge session 的 documentId / assetIdaiAccessScope.allowedResourceIds 绑定。调用方如果显式传入另一个已注册 Office session,存在越过当前 target resource scope 写入非授权资源的风险。

证据

  • rust/crates/mnote-web/src/routes/hermes_tools.rs 注册了多组 mnote.onlyoffice.* 读写工具。
  • rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rsrun_write_action() 调用 ensure_write_authorized() 后立即解析 session 并执行 bridge command。
  • rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rsresolve_session_id() 只解析显式 session 或全局 current session,没有验证该 session 对应的 resource 是否在 allowedResourceIds 内。
  • 对照 rust/crates/mnote-web/src/hermes_tools/resource.rsmindmap/resource 工具已有 ensure_resource_scope_allowed() 校验 allowedResourceIds / objectIdentity

影响

  • Page AI 当前 target 是资源 A 时,模型或恶意调用可传入资源 B 的 bridgeSessionId,尝试写入 B。
  • 写入审计会显示工具有写权限,但缺少“写的是哪个 resource、是否被 allowedResourceIds 授权”的闭环。
  • 多 tab session fallback 与本 bug 叠加时,错误写入更难被用户发现。

最小复现建议

  1. 构造 aiAccessScope.allowedResourceIds = [A]
  2. 注册两个 ONLYOFFICE bridge sessionA 和 B。
  3. 调用 mnote.onlyoffice.sheet.set_valuemnote.onlyoffice.document.insert_text,显式传 B 的 bridgeSessionId
  4. 期望:返回 403;当前风险:只要通用写权限通过就可能执行。

修复建议

  • BridgeSessionInfo 暴露稳定 documentIdassetIdobjectIdentity
  • ONLYOFFICE live 工具增加与 resource 工具等价的 scope 校验。
  • 写工具返回 receipt 时包含 resourceKind=officedocumentIdassetIdonlyofficeSessionId 与 permission decision。
  • dry-run 也必须执行 scope 校验,不能只返回 wouldWrite。

本轮进展

  • 2026-05-31
    • onlyoffice_live.rs 已在读/写 action 执行前校验 explicit session 对应的 sessionId / documentId / assetId / resource:office:{documentId}:{assetId} 是否包含在 aiAccessScope.allowedResourceIds
    • 缺失 aiAccessScope 或空 allowedResourceIds 现在返回 403,不再兼容放行;dry-run 同样执行该 scope 校验。
    • manifest.rs 已为 OnlyOffice live 工具声明 aiAccessScope.allowedResourceIdsonlyofficeSessionId / bridgeSessionIdanyOf 合同。
    • 已补 Rust 定点测试覆盖 allowedResourceIds=[asset_a] 时禁止写入 session asset_ballowedResourceIds=[asset_allowed] 时允许生成 dry-run plan,缺 scope 时返回 403,以及 manifest 合同。
    • 已补并通过 scripts/task515-onlyoffice-live-scope-http-smoke.jsHTTP 层覆盖缺 explicit session 400、scope 不匹配 403、缺 scope 403、授权 scope dry-run 200。
  • 2026-06-01
    • resource scope candidate 已同时接受 resource:office:{documentId}:{assetId} 与 FileTree / resource 对象侧使用的 resource:onlyoffice:{documentId}:{assetId},避免真实 Page AI target 使用 OnlyOffice object identity 时被误拒。
    • 已补并通过 scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js:真实 ONLYOFFICE iframe / DocumentServer 下,显式传入 Office B 的 bridgeSessionIdaiAccessScope.allowedResourceIds=[Office A] 时返回 403;授权 B 的 assetId / resource:onlyoffice:{documentId}:{assetId} 时 dry-run 返回 200。
    • document-resource-tab-runtime.js / sidebar-page-ai-runtime.js / hermes_client.rs 已补最小 target scope 链路:Office resource target 可把 onlyofficeSessionId 写入 target package,服务端 sanitize 不再丢弃 primaryTargetIdtargets[]assetIdonlyofficeSessionId,本地 run instructions 的 aiAccessScope.allowedResourceIds 会包含 Office asset、session 和 resource:onlyoffice:{documentId}:{assetId}。新增 hermes_client_run_body_preserves_onlyoffice_target_scope 证明服务端不再把 Office target scope 降级为当前页面 id。
    • 本地 agent instructions 已明确要求从 agentRunEnvelope.targetPackage.onlyofficeSessionId 或对应 target 取值传给 mnote.onlyoffice.* 工具,不允许 fallback 到最近活跃 Office session。
    • task518 已扩展并通过授权 B session 的非 dry-run 写入验证:写入后 document.export 证明 B 包含唯一 marker,A 不包含,补齐真实 iframe 层写入落点证据。
    • 已补并通过 scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js:真实 Page AI UI 选择 Office resource target 后,run payload 中 editorTarget.onlyofficeSessionIdtargetPackage.onlyofficeSessionIdtargetPackage.targets[0].onlyofficeSessionId 均等于 iframe live bridge sessionallowedFiles 只包含选中 Office 文件路径,且无 buffer-state 404 / console error。

当前 task518 已覆盖真实 iframe session + 工具层 resource scope + 非 dry-run 写入落点,task523 补齐真实 Page AI UI target picker 到 live Office session 的 run payload 绑定;组合后覆盖“UI 只授权选中 Office target,工具层拒绝未授权 session”的端到端安全边界。

验收

  • Rust 单测覆盖 allowedResourceIds 不包含 session resource 时写工具返回 403。
  • Rust 单测覆盖 allowedResourceIds 包含 assetIdobjectIdentity 时允许执行。
  • Rust 单测覆盖缺失 / 空 resource scope 时拒绝。
  • HTTP smoke 覆盖工具层 session/scope 边界。
  • Browser smoke 覆盖真实 ONLYOFFICE iframe session 的工具层 A/B resource scope 边界。
  • Rust 单测覆盖 Page AI Office target package 生成的 aiAccessScope 包含 Office asset/session/object identity。
  • Browser smoke 覆盖真实 Page AI UI 选择 Office target 后只把该 Office 的 live session 与 relativePath 冻结进 run payload;工具层 A/B session 越权拒写由 task518 覆盖。