Files
mnote/bugs/07-ai/done/7-46-onlyoffice-live-tool-resource-scope-bypass-v1.md
T

67 lines
6.2 KiB
Markdown
Raw Normal View History

2026-06-01 09:29:12 +08:00
# 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` 覆盖。