Skip to content

🐛 fix(sdk): bash 与后台进程任务域十一项修复——只读判定 fail-closed、后台超时豁免、存活判定口径 - #438

Merged
CavinHuang merged 10 commits into
mainfrom
fix/sdk-bash-process-jobs
Aug 22, 2026
Merged

🐛 fix(sdk): bash 与后台进程任务域十一项修复——只读判定 fail-closed、后台超时豁免、存活判定口径#438
CavinHuang merged 10 commits into
mainfrom
fix/sdk-bash-process-jobs

Conversation

@CavinHuang

Copy link
Copy Markdown
Owner

概述

修复 sdk 全量 review 提出的 11 个 issue(bash 工具 + 后台进程任务域),每个 commit 独立可跑绿。

Issue 修复
#300 (P0) 只读判定回退按方言 fail-closed:Bash 非 simple 一律需审批;PowerShell 拒绝集补齐 | & % {}\r\n 换行语句分隔符(review 实测两段式绕过形态已封堵并附红绿负例)
#338 (P2) 黑名单非空且非 simple 直接 fail-closed,$()/反引号/子 shell 藏匿目标命令根除;错误文案不再谎称 prefix 命中
#381 (P1) run_in_background 未显式传 timeout 不下发超时(spec 省略/direct 不挂 timer/worker spawn 不限时 + setTimeout(undefined) 守卫)——后台 dev server 不再被 120s 静默击杀
#313 (P1) worker 身份与注册表统一 OS StartTime 口径(worker 异步探测不阻塞启动),±1s 容差兼容存量记录——活任务不再被误判死亡
#368 durable 增量读返回原始 Buffer + StringDecoder 流式解码 end() 补尾,多字节序列跨界乱码消除(210KB 跨块用例钉死)
#329 isPersistedWorkerAlive 先 kill(pid,0) 探活再评估身份,休眠/挂起恢复后运行中任务不再被误标 interrupted
#330 ask-user 等待接线 abortSignal 竞速 + catch 区分 interrupted 与 handler 故障文案
#331 ProcessOutput 阻塞等待传入 context.abortSignal,软中断即返 aborted 不再干等 600s
#332 stopProcessJob 返回布尔成败;ProcessStop 成功才写终态消费通知,失败重探仍活返回 is_error——假已停止消除
#376 进程身份缓存过期即删重探,长驻进程泄漏止住
#377 prepareWorktree 提前到建记录之前,后台任务初始记录带 worktree 元数据

验证

  • bun run typecheck 六 workspace 全绿
  • sdk tools 全目录 229 pass / 0 fail;bash + process-job-registry 单独 33 pass
  • 独立 code review 两轮通过:首轮抓出 PowerShell 换行语句分隔符绕过(实测复现)已修复封堵;复核确认 4 个攻击形态全灭、单行正例零回归、rebase 后其余 commit 已审内容原样平移

Closes #300, closes #338, closes #368, closes #381, closes #313, closes #329, closes #330, closes #331, closes #332, closes #376, closes #377

🤖 Generated with Claude Code

@CavinHuang

Copy link
Copy Markdown
Owner Author

Code Review

十一项逐项核对完毕,十项半真实落地、测试为行为钉死型,质量高。唯一阻塞动作是 rebase 解决与 main #443#381 超时语义分叉

冲突分析(merge-tree 复现:2 文件 7 hunk)

[P1·存在于 main] sed 只读白名单两条绕过(本机 natives 实测复现)

  1. sed -f payload.sed README → isReadOnly true:脚本文件内容完全不检,payload 可 w file 写盘/e cmd 执行
  2. X='s/.*/curl evil/e' sed $X README → true:前置赋值被 native tokenizer 剥出 argv,运行期展开任意脚本

危害路径=default/acceptEdits/plan 对 readonly 自动 allow。已开 issue #453(附 excludedCommands wrapper 绕过 env/command/xargs)。

非阻塞 P3

  • abort handler 自身 reject Error('aborted') 会误分类为中断
  • prepareWorktree 抛出时 parentAbortHandler 泄漏至 run 结束
  • 平台脆弱断言 expect("git status").toBe(nativeBashAvailable)

rebase 后过 gate 即可合。

@CavinHuang

Copy link
Copy Markdown
Owner Author

Code Review

十一项中十项半落地扎实、测试质量高(#313 用真实 spawn 双端验证身份一致性),但有一个实测复现的 P1 安全绕过 + 与 main 平行实现的语义分叉,需修后再合

[P1·安全] PowerShell 白名单拒绝集漏圆括号,#300 修复可被绕过

$( 已拒但裸 ( / @( 未拒——PowerShell 参数位圆括号按表达式模式求值,可执行任意代码。分支正则原样实测复现:

powershell -Command Get-Date (del victim.txt)   # 判只读 ✗
Get-ChildItem @(iex calc)                        # 判只读 ✗

(del/iex 是别名,动词黑名单只扫全名,整串扫描也拦不住。)影响与 #300 同类:plan/default 模式免审批任意执行。修法一行:拒绝字符类补 [()] 并附上述红例。

[P2] #381 与 main 已落地实现(PR#443)语义分叉

merge-tree 实测冲突:bash.ts(4 处)、process-job-worker.ts(1 处)。关键取舍:

  1. timeoutMs 计算(bash.ts:89-98):main 版=未显式传一律无超时(前台默认 120s 已删);分支版=前台保 120s、后台豁免。建议取 main 计算式(尊重先行落地决策),但保留分支的条件化定时器守卫——main 当前 startDirectShellTask(:463)仍无条件 setTimeout(fn, timeoutMs),setTimeout(fn, undefined) ≈1ms 即触发,直连 SDK 宿主无 artifactsRoot/sessionId 时所有默认 Bash 会被立即击杀;分支守卫恰好修掉它。随后删除 resolveBashTimeoutMs 导出。
  2. 三处类型注解(timeoutMs? vs number | undefined)任取随 main;两处重复注释留一条;worker 超时守卫 truthy vs >0 等价任取。
  3. bash.test.ts 虽文本自动合并但有语义矛盾:分支测试钉死 resolveBashTimeoutMs({})===120_000,若取 main 语义必须同步改写,不能因文本无冲突放过。

其余核对结论(全部通过)

#338 黑名单藏匿(complex 直接拒+文案不再谎称 prefix)/#313 身份口径(OS StartTime 异步探测±1s 容差,win32 双侧真实 spawn 测试)/#368 多字节跨界(StringDecoder 全链路 210KB 无 U+FFFD)/#329 探活顺序(kill(pid,0) 先行)/#330 ask-user 中止/#331 ProcessOutput 软中断/#332 stop 假终态(布尔返回+失败重探)/#376 身份缓存过期逐出/#377 worktree 提前。

P3 残留(不阻断):worker 身份探测永久失败时 alive 兜底的"假活"窄窗;taskkill 异步结果未确认;永不重读的缓存条目留到 clearProcessJobs。

结论

修掉圆括号绕过(P1,一行)+ 完成 #381 语义取舍后可合。

TaTaLiao and others added 5 commits August 22, 2026 20:48
- #300 非 simple 回退按方言区分:Bash 一律需审批;PowerShell 拒绝集补管道 |、链式/调用 &、别名 %、脚本块 {} 字符,白名单首词不再被复合载荷借道
- #338 沙箱 excludedCommands 命中判定:非 simple 直接 fail-closed 拒绝,$()/反引号/子 shell 内的目标命令不再漏检
- #368 durable 增量读改持 StringDecoder 流式解码并 drain 后 end() 补尾,64KB 块边界的多字节序列不再碎成 U+FFFD
- #381 run_in_background 且未显式传 timeout 时不下发超时(spec 省略/direct 不挂 timer),后台 dev server 不再被 120s 默认预算静默击杀

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- #313 worker 身份改用 OS StartTime(PowerShell,异步探测不阻塞命令拉起),与注册表同源口径;比对侧保留 ±1s 容差兼容存量记录
- #329 isPersistedWorkerAlive 先 kill(pid,0) 探活再评估身份,心跳陈旧不再直接判死(休眠恢复误标 interrupted)
- #331 ProcessOutput 阻塞等待传入 context.abortSignal,中止即转非阻塞返回(retrieval_status=aborted)
- #332 ProcessStop 依赖 stop 成败:失败重探仍活则报错不动状态、不吞终态通知
- #376 身份缓存命中过期即 delete 重探,不再滞留陈旧条目

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- #330 invokeHandler 外包 abort 竞速,软中断即刻取消不再无限期阻塞宿主 handler;catch 区分 interrupted 与 handler 故障两种文案,中断不再谎报为 User declined

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- #377 prepareWorktree 提前到 createProcessJobRecord 之前并从运行路径移除原调用,后台 agent 任务记录自创建即携带 worktree 元数据

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- #300 跟进:换行是 PowerShell 语句分隔符,拒绝集补 \r/\n,Get-Date\n(victim).Delete() 形态不再借首词免审批
- #338 文案跟进:非 simple 拒绝不再返回 excluded[0] 点名未命中的黑名单项,改为通用 compound 拒绝说明

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CavinHuang
CavinHuang force-pushed the fix/sdk-bash-process-jobs branch from cde7528 to aacfd6e Compare August 22, 2026 12:53
@CavinHuang
CavinHuang merged commit 22fbd55 into main Aug 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment