Skip to content

fix(security): P0 安全修复——cron/hooks 门控、WS 握手超时、seed 工具审批、tunnel IP 过滤 - #511

Open
zayokami wants to merge 3 commits into
Stack-Cairn:mainfrom
zayokami:feat/security-p0-fixes
Open

fix(security): P0 安全修复——cron/hooks 门控、WS 握手超时、seed 工具审批、tunnel IP 过滤#511
zayokami wants to merge 3 commits into
Stack-Cairn:mainfrom
zayokami:feat/security-p0-fixes

Conversation

@zayokami

@zayokami zayokami commented Aug 16, 2026

Copy link
Copy Markdown

背景

并行安全审计(8 维度挖掘 + 12 条高危对抗性验证 + 人工复核)确认的 4 条高危链路(issue #512)。每条修复都带复现测试:先在未修复代码上证明漏洞存在(测试失败),修复后转绿。

Closes #512

Screenshots / preview

本次改动为运行时逻辑(工具审批门、协议门控),无 UI 视觉变化;以下是四项修复的复现测试证据(before/after):

P0 修复验证证据

修复内容

1. 远程 cron/hooks 任意命令执行(confirmed×2,最严重)

  • 漏洞:浏览器直通白名单中 CronManage 无任何功能开关门控(terminal/git/tunnels/SFTP 均有),持网关 token 者可提交并立即执行任意 bash 脚本;本地也无任何设置可禁用
  • 修复:新增 enable_web_automation 后端强制开关
    • 网关 guard.go:CronManage 移入门控分支,fail-closed
    • 桌面 envelope_handler.rs:镜像检查(403 纵深防御)
    • 另修复 run_now 绕过 enabled 检查的缺陷
  • 测试:v2_cron_gating_test.go(修复前:开关关闭仍放行转发)

2. WS 预认证静默连接槽位耗尽 DoS(confirmed×2)

  • 漏洞:/ws/v2/agent/ws/v2/terminal 升级后无读超时,257 条裸连接即可打满全部槽位
  • 修复:升级后立即设握手窗口(IdleTimeout,与浏览器链路同公式);认证完成后清除,由既有心跳维持存活,不误杀健康连接
  • 测试:v2_handshake_deadline_test.go(修复前:静默连接永不关闭;另含认证后空闲连接存活回归测试)

3. <seed:tool_call> 恢复路径绕过工具审批门(confirmed×2)

  • 漏洞:提示注入可诱导模型输出 seed 标记文本,恢复执行直接跳过 resolveToolGate(ask 审批卡片/deny 策略全部失效)
  • 修复:恢复循环与结构化调用同权过门;拒绝时 reason 作为 toolResult 返回模型
  • 测试:agent-runner.test.mjs 两个 gate 用例(修复前:工具直接执行)

4. Tunnel 目标允许云元数据/保留段 SSRF(plausible×2)

  • 漏洞:validate_tunnel_target_url 只拒绝非 IP 主机名,169.254.169.254 云元数据/保留段全放行
  • 修复:与网关 Go 侧对齐的 IP 黑名单(link-local/元数据/多播/保留/广播);localhost、回环、RFC1918/ULA 保持放行(暴露本地服务是 tunnel 本职);数据面 proxy.rs 复用同一校验
  • 测试:validate_tunnel_target_url 拒绝用例(修复前:169.254.169.254 通过)

回归

结果
Go(网关) ✅ 全过(仅基线 Windows 权限测试失败,与本次无关)
Rust(桌面) ✅ 改动域全过;12 个失败为基线环境问题(shell/git 行为),干净 main 上同样失败
前端 ✅ 改动域全过;5 个失败为基线,干净 main 上同样失败
git diff --check / gofmt / biome ✅ 干净(仅仓库既有 CRLF)

行为变更说明

新增 Remote 设置 enable_web_automation(默认关闭,fail-closed):开启后远程 WebUI 才能管理 cron/hooks。WebUI 的设置 UI 可在后续 PR 中补充开关入口。

🤖 Generated with Claude Code

安全审计(并行 8 维度 + 对抗性验证)确认的 4 条高危链路,全部带复现测试修复:

1. 远程 cron/hooks 任意命令执行(confirmed×2):CronManage 直通臂此前无任何
   功能开关门控(terminal/git/tunnels 均有)。新增 enable_web_automation 后端
   强制开关:网关 guard.go 门控 + 桌面端 envelope_handler 镜像检查(403),
   设置未同步时 fail-closed。另修复 run_now 绕过 enabled 检查的缺陷。
   - 测试:v2_cron_gating_test.go(修复前:开关关闭仍放行转发)

2. WS 预认证静默连接槽位耗尽 DoS(confirmed×2):/ws/v2/agent 与
   /ws/v2/terminal 升级后无任何读超时,无凭据连接可永久占用槽位。
   升级后立即设置握手窗口(IdleTimeout,与浏览器链路同公式),
   认证完成后清除(存活由既有心跳机制维持,不误杀健康连接)。
   - 测试:v2_handshake_deadline_test.go(修复前:静默连接永不关闭)

3. <seed:tool_call> 恢复路径绕过工具审批门(confirmed×2):提示注入可诱导
   模型输出标记文本,恢复执行绕过 resolveToolGate(ask/deny 策略)。
   恢复循环现在与结构化调用同权过门,拒绝时 reason 作为 toolResult 返回。
   - 测试:agent-runner.test.mjs gate 用例(修复前:工具直接执行)

4. Tunnel 目标允许云元数据/保留段 SSRF(plausible×2):validate_tunnel_target_url
   只拒绝非 IP 主机名,169.254.169.254/保留段全放行。新增与网关 Go 侧对齐的
   IP 黑名单(link-local/元数据/多播/保留/广播),localhost、回环与 RFC1918/ULA
   保持放行(暴露本地服务是 tunnel 本职);数据面 proxy.rs 复用同一校验。
   - 测试:validate_tunnel_target_url 拒绝用例(修复前:169.254.169.254 通过)

回归:Go/Rust/前端测试在改动域全部通过;剩余失败均为基线环境问题
(Windows 权限位、shell/git 行为、web/dist 缺失),与本次改动无关。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@StackCairn
StackCairn marked this pull request as draft August 16, 2026 11:03
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

PR governance checks passed. Awaiting human review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zayokami
zayokami marked this pull request as ready for review August 16, 2026 11:06
@su-fen

su-fen commented Aug 16, 2026

Copy link
Copy Markdown
Member

审核结论:当前不建议直接合入

本次审核基于精确头提交 a0256c976e03ff20798939af0af9a6438cfafa7f。当前 CI、PR Governance 均已通过,GitHub 也显示 MERGEABLE / CLEAN;但代码层仍有以下 4 个阻断问题,绿色检查没有覆盖这些合同和对抗路径。

1. 阻断·安全:seed 审批门仍晚于可执行 Hook 事件

agentRunner.ts#L1839 先调用 onToolExecutionStart,直到 agentRunner.ts#L1846 才执行 resolveToolGate

该回调会在 runAgentConversationTurn.ts#L969 派发 tool_execution_start,而 Hook 可以在 hookRunner.ts#L50 立即执行 Bash,或执行 HTTP 请求。

因此,提示注入产生的 seed 工具调用即使随后被用户拒绝,Hook 副作用也已经进入执行队列,仍存在审批前副作用链。拒绝分支还直接 continue,没有调用 onToolResult,会缺失 tool_execution_end 并可能留下运行中状态。

建议:把 seed resolveToolGate 移到 onToolExecutionStart、执行状态和 Hook 派发之前;拒绝时补齐 onToolResult,并增加“拒绝不会触发 Hook Bash/HTTP”的回归测试。

2. 阻断·安全:Tunnel 元数据过滤可被 IPv4-mapped IPv6 绕过

tunnel/mod.rs#L67 分别处理 IPv4/IPv6,但 IPv6 分支没有先执行 to_ipv4_mapped() 或等价 unmap。

对抗验证:

URL: http://[::ffff:169.254.169.254]:80/latest
url host: [::ffff:a9fe:a9fe]
mapped IPv4: 169.254.169.254
blocked_by_pr_predicate: false

也就是说,目标仍可映射到云元数据地址。仓库 Go 侧已有正确先例:outbound_http.go#L135 会先 Unmap()

建议:统一规范化映射地址后再应用 IPv4 黑名单,并新增 http://[::ffff:169.254.169.254]/ 的创建路径与数据面测试。

3. 阻断·功能合同:enableWebAutomation 没有进入共享设置/UI 链路

Rust 后端新增了默认 false 的字段,但共享 settings/types.ts#L485 没有该字段;settings/index.ts#L572 的归一化会丢弃它;settings/sync.ts#L51 的双向同步也不包含它;Remote 页面在 RemoteSection.tsx#L541 结束于 Tunnel 开关。

实际诊断结果:即使输入 { enableWebAutomation: true },归一化结果也没有该字段,构建出的同步载荷同样省略它。正常 UI 保存会使后端字段继续保持/落回默认 false,导致 CronManage 没有受支持的开启方式,远程 cron/hooks 实际被永久禁用。

建议:补齐共享类型、默认值、归一化、双向同步、桌面 Remote UI、i18n,以及保存/重载/同步的 round-trip 测试。

4. 阻断·功能回归:禁用任务不再允许手动 Run Now

scheduler.rs#L289 新增了 !task.enabled 拒绝,但既有测试 automation/tests.rs#L369 明确规定“禁用且剩余次数为 0 的任务仍可作为手动运行上下文”;UI 的 Run Now 按钮在 CronTaskViewModal.tsx#L151 也不会因 task.enabled=false 禁用。

PR 中新旧两个相反语义的测试可以同时通过,是因为一个只验证 Store 能读取,另一个在 Scheduler 层拒绝,并没有解决合同冲突。enabled 原本控制定时调度,并不是本地手动执行的授权边界。

建议:恢复本地手动执行的既有语义;如果只希望远程 run_now 拒绝禁用任务,应在远程 cron.manage 边界单独限制,而不是改变全局 Scheduler 行为。

已完成验证

  • 最新 main 合并预演、git diff --check:通过
  • Gateway go test ./...:通过
  • Rust Tunnel:2/2;Automation:29/29;Settings:50/50
  • Agent Runner:42/42;Settings normalization:79/79
  • GUI / WebUI production build:通过
  • UI boundary:8/8

这些测试证明新增 happy-path 可以工作,但没有覆盖上述审批事件顺序、IPv4-mapped IPv6、设置字段可达性和既有手动运行语义。

建议修复以上问题并推送新 SHA 后,再重新执行 CI 和对抗性审核。

…开关全链路、恢复本地 Run Now 语义

针对 PR Stack-Cairn#511 审核的 4 个阻断问题的修订:

1. seed 审批门先于可执行事件:resolveToolGate 移到 onToolCall/onToolExecutionStart
   之前(后者会派发 tool_execution_start,Hook 据此立即执行 Bash/HTTP);拒绝时
   补齐 onToolResult(toolResultReceived 配对,不留下运行中状态)。回归测试断言
   拒绝路径不触发 onToolExecutionStart、且拒绝原因经 onToolResult 到达 transcript
   (旧顺序下该测试失败=复现)。

2. tunnel 目标过滤 unmap:is_blocked_tunnel_target_ip 的 IPv6 分支先
   to_ipv4_mapped() 还原再走 IPv4 黑名单(对齐网关 Go 侧 outbound_http.go 的
   Unmap() 先例),http://[::ffff:169.254.169.254]/ 不再绕过元数据段拦截。
   创建路径与数据面共用同一校验,新增 mapped 拒绝/放行用例(禁用检查时测试
   失败=复现)。

3. enableWebAutomation 全链路:补齐共享 types.ts、normalizeRemoteSettings、
   默认值、mergeSyncedRemoteSettings、buildGatewaySettingsSyncPayload、桌面
   RemoteSection 开关(Zap 图标)、zh/en 标题与双端 Hint(i18n 校验要求两 host
   同时提供)。新增 TS round-trip 测试(归一化→同步载荷→应用→再归一化,
   缺字段载荷不覆盖 true)+ Rust 持久化重载断言 + WebUI 同步载荷断言。

4. 恢复本地 Run Now 语义:scheduler.run_now 撤销 enabled 拒绝(enabled 只控制
   定时调度,UI 的 Run Now 按钮不因 enabled=false 禁用);远程限制移到
   gateway_bridge::handle_cron_manage 的 run_now 边界(ensure_remote_run_now_allowed),
   禁用任务不可经 WebUI 远程触发。删除与语义冲突的旧测试,新增远程策略测试
   (拒绝/放行 + 本地 store 仍可加载禁用任务为手动上下文)。

回归:Rust 域 112/112;GUI 前端 1850/1855(5 个为既有 Windows 基线失败,
干净树同样失败);WebUI 578/578;i18n 共享键 5/5;diff --check 干净。
UI 边界检查的 3 条告警在干净基线同样存在(Windows 环境预存)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zayokami

Copy link
Copy Markdown
Author

已按审核意见修订 4 个阻断问题,新 SHA 已推送(f5c6759)。

  1. seed 审批门顺序:resolveToolGate 移到 onToolExecutionStart/onToolCall 之前(拒绝先于 tool_execution_start 派发,Hook 无法在执行前触发);拒绝分支补齐 onToolResult(配对 toolResultReceived,无悬挂运行中状态)。回归测试断言拒绝路径不触发 onToolExecutionStart、拒绝原因经 onToolResult 到达 transcript——旧顺序下该测试失败(已复现)。

  2. Tunnel mapped-IPv4:is_blocked_tunnel_target_ip 的 IPv6 分支先 to_ipv4_mapped() 还原再走 IPv4 黑名单(对齐网关 outbound_http.go 的 Unmap() 先例);http://[::ffff:169.254.169.254]/ 创建路径与数据面(共用同一校验)均被拒绝,新增 mapped 拒绝/放行测试,禁用检查时测试失败(已复现)。

  3. enableWebAutomation 全链路:共享 types.ts 字段、normalizeRemoteSettings、默认值、mergeSyncedRemoteSettings、buildGatewaySettingsSyncPayload、桌面 RemoteSection 开关(位于 Tunnel 之后,Zap 图标)、zh/en 标题与双端 Hint(i18n 校验要求两 host 同时提供)。新增 round-trip 测试:TS 归一化→同步载荷→应用→再归一化、缺字段旧载荷不覆盖 true;Rust 持久化/重载断言;WebUI 同步载荷断言。GUI 实测保存后字段不再被丢弃。

  4. 恢复本地 Run Now 语义:scheduler.run_now 撤销 enabled 拒绝(enabled 只控制定时调度,与既有 manual_run_context_allows_disabled_exhausted_task 合同一致);远程限制移到 handle_cron_manage 的 run_now 边界(ensure_remote_run_now_allowed)——禁用任务不可经 WebUI 远程触发,本地 UI 行为不变。删除冲突测试,新增远程策略测试(拒绝/放行 + 本地 store 仍可加载禁用任务)。

回归:Rust 域 112/112;GUI 前端 1850/1855(5 个失败为 Windows 基线预存,干净树同样失败);WebUI 578/578;i18n 共享键校验 5/5;git diff --check 干净。UI 边界检查的 3 条告警在干净基线同样存在(Windows 环境预存)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants