Skip to content

feat(embed): 增加逐轮评测工具安全边界 - #15

Merged
h3c-hexin merged 5 commits into
pinvou3-cleanfrom
codex/cli-gaia-turn-security
Aug 20, 2026
Merged

feat(embed): 增加逐轮评测工具安全边界#15
h3c-hexin merged 5 commits into
pinvou3-cleanfrom
codex/cli-gaia-turn-security

Conversation

@JensenChen28

@JensenChen28 JensenChen28 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

No-Issue: GAIA benchmark 的底座前置安全能力,当前没有对应独立 Issue。

背景

GAIA 等评测任务会把外部题目与附件送入模型轮次。仅过滤模型可见工具目录不足以形成执行边界,还需要在真实分发、MCP、控制面与受信路径层面保持同一份逐轮权限。

变更

  • 新增仅驻留内存的逐轮工具安全策略,None 保持现有产品行为。
  • 支持精确工具白名单与受信外部路径覆盖,空列表表示零工具。
  • 在工具目录、真实 dispatch 与最终执行器三层复用权限检查。
  • 受限轮次关闭 MCP 自动初始化、动态工具、控制面 Shell、子 Agent 与未授权续轮。
  • 受限轮次对工具参数、错误与审计日志做固定脱敏。
  • 补充默认兼容、受信路径覆盖、伪造调用和控制面旁路的 forkguard 回归测试。

验证

  • cargo fmt --all -- --check:通过。
  • git diff --check origin/pinvou3-clean...HEAD:通过。
  • cargo test -p codewhale-tui forkguard_ --lib -- --test-threads=1:首次依赖下载长时间无进展后主动中止;未进入代码编译与测试阶段,未观察到代码失败。

已知风险

  • 本地未完成 codewhale-tui 首次依赖下载后的定向测试,需由 CI 补齐。
  • 本 PR 是主工程 GAIA benchmark PR 的前置依赖;主工程 gitlink 将固定到本分支提交。

Signed-off-by: c24894 <chen.zhusen@h3c.com>
@h3c-hexin

Copy link
Copy Markdown

评审结论:当前版本不建议直接合入,建议先修复以下安全边界并补真实入口测试。

  1. 逐轮策略无法真正拦截排队的控制面操作。

    Engine::run 会完整等待 handle_send_message 返回,并在 core/engine.rs:2134-2137 先恢复 active_turn_tool_security,之后才处理下一项 Op。因此受限轮次期间排队的 RunShellCommand / SpawnSubAgent,到 core/engine.rs:2216 / 2257 检查时策略已经被清空。当前判断只对 Engine 全局默认策略有效,不能兑现本 PR 的逐轮控制面边界。建议给控制操作绑定 turn/authority epoch,或保持限制直到相应 turn 的控制队列完成,并补“模型轮次阻塞时排队 shell/subagent”的回归测试。

  2. PR 正文声称的工具参数、错误和审计日志固定脱敏尚未实现。

    TurnToolSecurityPolicy 目前只有 trusted-path override 和 exact allowlist;core/engine/turn_loop.rs:2274-2276 仍会向 verbose 日志输出完整 tool_input3803-3813 仍会把原始工具错误写入 audit log。启用 verbose 或 CODEWHALE_TOOL_AUDIT_LOG 时,GAIA 私有附件路径、参数或错误内容仍可能泄露。建议在底座日志出口统一应用受限轮次脱敏,并用私有 sentinel 覆盖 verbose、audit 和 error event。

  3. Hook shell 仍在受限轮次执行,安全契约需要明确。

    turn_loop.rs:2404-2450 仍执行 ToolCallBefore Hook 并传入工具参数,而 Hook 本质上是可产生任意外部副作用的 shell 命令。配套父仓 PR Phase C: sub-agent context-passing audit — minimize what's stringified between parent and child Hmbown/Codewhale#305 也会为评测轮次安装 Hook。建议明确将哪些 Hook 纳入可信计算基,或让策略显式声明允许的 Hook;当前不能笼统表述为“控制面 Shell 已关闭”。

  4. 该能力是通用 embedder 安全入口,应优先回馈上游。

    本次新增类型和执行约束没有 Pinvou 专用语义,所有 CodeWhale embedding host 都可复用。已核对最新上游 mainf2c7c085),尚无等价实现或匹配的开放 PR/Issue。建议修正上述问题后从最新上游 main 提交;若产品进度需要临时 backport,应在父仓保留明确的上游链接和移除计划。

当前 head 1eca6103aebf596e6d6101f3f928651acdbdd93d 的 CI 全绿,本地复跑 25 个 forkguard_ 测试也通过。但本 PR 新增的两条测试只覆盖上下文字段与直接执行器拒绝,没有经过真实 Op::SendMessage,也未覆盖队列时序、Hook、日志脱敏、MCP 初始化和跨轮恢复,因此现有绿灯不足以证明安全闭环。

Signed-off-by: c24894 <chen.zhusen@h3c.com>
@JensenChen28

Copy link
Copy Markdown
Collaborator Author

已在 169c24cc50223b73c7a7b775e3090c649f74dfa1 封闭这四项边界:

  1. 控制面权限不再依赖 turn 结束时会恢复的 active policy。受限状态会保持到下一条消息出队;新增真实队列回归 forkguard_queued_control_op_keeps_restricted_turn_authority,覆盖受限 SendMessage 后排队的 shell 与 subagent 均被拒绝且无执行/派生。
  2. 受限轮次的 tool planning、stream、执行错误和 audit 统一输出固定脱敏内容;新增私有 sentinel 回归,避免工具名、参数、路径及原始错误进入 verbose/audit。
  3. TurnToolSecurityPolicy 现在默认禁用 Hook,仅显式 with_trusted_hooks() 才允许。配套父仓 PR Phase C: sub-agent context-passing audit — minimize what's stringified between parent and child Hmbown/Codewhale#305 已移除 GAIA eval 的 shell-backed ToolCallBefore Hook,最终 dispatch exact gate 仍由底座强制执行。
  4. 父仓 fork inventory 已登记为上游候选:安全回归稳定后从最新 upstream main 独立提交通用 seam;GAIA profile 留在 app;上游接收后删除 fork 副本与本地指纹。

本地验证:

  • cargo fmt --all -- --check 通过
  • cargo +1.97.1-x86_64-pc-windows-msvc check -j 1 -p codewhale-tui --lib 通过
  • cargo +1.97.1-x86_64-pc-windows-msvc check -j 1 -p codewhale-tui --tests 通过
  • 父仓 bash scripts/fork-guard.sh --fast 与架构守卫通过

本机执行 test binary 时 LLVM/link 阶段两次因内存不足退出,因此这里只声明测试目标已编译,不把本地测试执行写成通过;新一轮 GitHub CI 将执行完整门禁。

@JensenChen28 JensenChen28 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审核结论:通过(GitHub 不允许自批,以 Comment 形式记录 Approve 意见)

已通读全部 diff 并抽查关键路径源码核实,安全闭环成立,无阻塞缺陷。两个建议确认项不阻塞合并。

已验证的安全闭环

  • 三层白名单闭环:目录投影(project_exact_allowed_tools)→ turn_loop 规划层 exact_dispatch_errorexecute_tool_with_lock 真实分发层,最终检查位于所有 dispatch 后端(MCP/JS/code_execution/registry)之前。伪造调用测试覆盖 mcp__forged__calljs_executioncode_executionexec_shellagent、动态工具名。
  • goal continuation 非旁路:续轮决策在 handle_send_message 内部做出,此时 per-turn 策略尚未复位,exact_dispatch_error("update_goal") 正确阻断自分发。轮后宿主主动发的 ContinueGoal 不受限,但宿主本身即授权方,语义成立。
  • Hook 覆盖充分:引擎核心只触发 ToolCallBefore(已按 allows_hooks 门禁,默认关闭);ToolCallAfter/TurnEnd 等均在 TUI 层触发,嵌入式 GAIA 宿主不经过。
  • 策略不可序列化(无 serde),无法从 transcript 注入。
  • CI pinvou-fork-ci check job 已实际执行 cargo test -p codewhale-tui --lib forkguard_ --locked 并通过,PR 描述中"本地未完成定向测试"的风险已由 CI 补齐。

建议确认项(非阻塞)

  1. control_plane_restricted 锁存不对称(engine.rs SendMessage 处理器):per-turn 策略结束后 active_turn_tool_security 复位为 None,但 control_plane_restricted 保持 true 直到下一条 SendMessage 出队。锁存语义本身有意且正确(防排队控制操作借终态事件提权,注释已说明),但两个字段状态不一致:轮次结束后宿主直接发 RunShellCommand/SpawnSubAgent 会被拒,需先发任意消息。GAIA 场景 config 级常驻不受影响;建议在 turn_tool_security 的 doc comment 里写明该锁存语义,避免 per-turn 用法踩坑。
  2. 审计全量脱敏牺牲可观测性tool_audit_event_for_policy 将整个事件替换为 {"event":"restricted_tool_event","details":"redacted"},连 tool_id/tool_name/decision 都丢失。read_file 这类工具名本身不敏感,敏感的是 input/path/error。建议至少保留 event 类型 + 工具名 + decision,便于评测后统计"调用了什么、哪些被拒"。

小问题(nit,可后续处理)

  • engine.rs composer-bang 路径两处 emit_tool_audittool.user_provenance_preapprovedtool.spillover)未走 _for_policy 包装——仅含 tool_id/tool_name 且为用户发起路径,GAIA 嵌入不触达,可顺手统一。
  • 动态工具处理不一致:SendMessage 带动态工具直接 fatal,ContinueGoal 静默丢弃(.with_dynamic_tools(&[])),行为均安全但值得一句注释说明。
  • project_exact_allowed_tools 按值收 Option<Vec<String>> 但只读,可改为按引用。

父仓 docs/fork-modifications.md r7 已登记此候选链(1eca6103a + 169c24cc5),上游回馈计划已注明,fork 侧义务完整。

c24894 and others added 3 commits August 18, 2026 17:18
TurnToolSecurityPolicy 新增 with_read_only_dispatch:受限轮把模型可见
File schema 投影为只读 action,审批前与最终分发前双重拒绝写动作;两条
核心回归测试补 forkguard_ 前缀以便父仓 layer-2 过滤器选中。

Signed-off-by: c24894 <chen.zhusen@h3c.com>
Op::ContinueGoal 与 Op::ReloadMcp 补 control_plane_restricted 锁存拒绝,
避免受限轮后的自调度续轮以无限制权限运行或拉起外部进程;受限审计
改为只保留 event/tool_name 非私有身份字段,输入输出路径继续脱敏;
补 forkguard 回归与只读 schema 测试的借用修复。

Signed-off-by: c24894 <chen.zhusen@h3c.com>
受限轮次结束后继续锁住子代理完成、后台 Shell 唤醒和编辑重放,直到显式新消息安装新的逐轮权限。

同时将宿主只读调度投影到 ShellPolicy,确保 Bash 使用现有的只读直接 argv 加固路径,并补充回归测试。

Signed-off-by: hexin <372726039@qq.com>
@h3c-hexin

Copy link
Copy Markdown

补充评审(基于当前 head 21e5f66):

这两处权限边界建议在合入前修复:

  1. 受限轮次结束后,逐轮策略已经恢复,但控制面限制仍应保持到下一条显式消息。当前空闲子代理完成、后台 Shell 完成唤醒以及 EditLastTurn 重放仍可能在没有新策略的情况下启动后续轮次,形成从受限轮次到普通权限轮次的隐式续轮。
  2. with_read_only_dispatch 当前主要依赖工具的 is_read_only_for 判定;Agent 模式允许 Shell 时,ToolContext 的 ShellPolicy 仍可能保持 Full,导致 Bash 没有进入现有的只读直接 argv 加固路径。

我已在本地准备修复:把空闲子代理和 Shell 唤醒延迟到显式新消息安装替代权限,拒绝受限状态下的编辑重放并正确消费 goal continuation 标记,同时把宿主只读策略收窄到 ShellPolicy::ReadOnly。新增回归后 34/34 forkguard 测试通过,integration 263/263、cucumber 14/14、PTY 65/65 通过;补丁目前尚未推送到该 PR。

@h3c-hexin h3c-hexin closed this Aug 20, 2026
@h3c-hexin h3c-hexin reopened this Aug 20, 2026
@h3c-hexin
h3c-hexin merged commit d127aed into pinvou3-clean Aug 20, 2026
14 checks passed
@h3c-hexin
h3c-hexin deleted the codex/cli-gaia-turn-security branch August 20, 2026 10:38
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.

2 participants