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 环境预存)。

@github-actions

Copy link
Copy Markdown
Contributor

This PR has been inactive for 14 days and is marked stale. It will be closed in 14 days if there is no further activity. Push an update or comment to keep it open.

@github-actions github-actions Bot added the Stale label Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants