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

67 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 7-46 ONLYOFFICE live tool 缺少 resource scope 绑定
## 状态
- 状态:done
- Owner07-ai / Hermes tools / OnlyOffice live bridge
- 发现时间:2026-05-31
## 现象
ONLYOFFICE live 写工具当前主要检查 `idempotencyKey``dryRun``aiAccessScope.permissionLevel` 和 command context 写权限,但没有把 bridge session 的 `documentId` / `assetId``aiAccessScope.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.rs``run_write_action()` 调用 `ensure_write_authorized()` 后立即解析 session 并执行 bridge command。
- `rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rs``resolve_session_id()` 只解析显式 session 或全局 current session,没有验证该 session 对应的 resource 是否在 `allowedResourceIds` 内。
- 对照 `rust/crates/mnote-web/src/hermes_tools/resource.rs`mindmap/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_value``mnote.onlyoffice.document.insert_text`,显式传 B 的 `bridgeSessionId`
4. 期望:返回 403;当前风险:只要通用写权限通过就可能执行。
## 修复建议
- `BridgeSessionInfo` 暴露稳定 `documentId``assetId``objectIdentity`
- ONLYOFFICE live 工具增加与 resource 工具等价的 scope 校验。
- 写工具返回 receipt 时包含 `resourceKind=office``documentId``assetId``onlyofficeSessionId` 与 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.allowedResourceIds``onlyofficeSessionId` / `bridgeSessionId``anyOf` 合同。
- 已补 Rust 定点测试覆盖 `allowedResourceIds=[asset_a]` 时禁止写入 session `asset_b``allowedResourceIds=[asset_allowed]` 时允许生成 dry-run plan,缺 scope 时返回 403,以及 manifest 合同。
- 已补并通过 `scripts/task515-onlyoffice-live-scope-http-smoke.js`HTTP 层覆盖缺 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 的 `bridgeSessionId``aiAccessScope.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 不再丢弃 `primaryTargetId``targets[]``assetId``onlyofficeSessionId`,本地 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.onlyofficeSessionId``targetPackage.onlyofficeSessionId``targetPackage.targets[0].onlyofficeSessionId` 均等于 iframe live bridge session`allowedFiles` 只包含选中 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”的端到端安全边界。
## 验收
- [x] Rust 单测覆盖 allowedResourceIds 不包含 session resource 时写工具返回 403。
- [x] Rust 单测覆盖 allowedResourceIds 包含 `assetId``objectIdentity` 时允许执行。
- [x] Rust 单测覆盖缺失 / 空 resource scope 时拒绝。
- [x] HTTP smoke 覆盖工具层 session/scope 边界。
- [x] Browser smoke 覆盖真实 ONLYOFFICE iframe session 的工具层 A/B resource scope 边界。
- [x] Rust 单测覆盖 Page AI Office target package 生成的 aiAccessScope 包含 Office asset/session/object identity。
- [x] Browser smoke 覆盖真实 Page AI UI 选择 Office target 后只把该 Office 的 live session 与 relativePath 冻结进 run payload;工具层 A/B session 越权拒写由 `task518` 覆盖。