Shell 工具是 agent 与宿主机之间权柄最大的接口。它同时是能力面(一切进程、文件、网络操作的入口)、 攻击面(提示注入可转化为任意代码执行)和体验面(超时、截断、后台化决定 agent 能否完成真实工程任务)。 8 个 harness 的源码显示,行业已经收敛出几组明确的设计张力:
sandboxing/src/denial.rs)、以及沙箱内工具链缺失。bash -lc <cmd>)是安全默认值:无残留状态、无跨调用污染。goose、ZCode、pi 默认采用。workdir 参数 + 后台进程表,把状态管理推给模型。bash -c 整串执行:零解析成本、语义 100% 保真。但权限判定只能看整串或首词。cat a && rm -rf / 拆成两段分别判定,只读段自动放行、写入段走审批。代价是解析器成为新的攻击面(别名、heredoc、命令替换、eval 都可能绕过解析)。opaque、ZCode 的 hasParseErrors)时,回退到整体询问,绝不回退到整体放行。justification 非空句),且升级路径必须 fail-closed——沙箱不可用时前台命令直接抛错,不允许无沙箱降级执行(dsh bash-sandbox 的设计)。isModelContentBounded、goose 的 tail preview)与流式缓冲上限(gemini 16 MB)两派。promoteOnTimeout(前台超时转后台而非杀死)代表"长任务不死"的设计取向。| 维度 | codex | dsh | gemini-cli | opencode | pi | mcode | ZCode | goose |
|---|---|---|---|---|---|---|---|---|
| 执行入口 | unified_exec 工具族(exec_command / write_stdin / terminal_session) | ShellExecutor 抽象 + tool-bash / tool-bash-persistent | ShellTool(node-pty) | shell 工具 + Shell 服务 | bash / powershell 工具 + 通用 execCommand | bash 工具(pi 派生)+ 后台 bash 执行器 | Bash 工具 + ExecutionPort 抽象 | developer 扩展的 shell 工具 |
| 默认调用模型 | 新进程/次 + 交互进程池 | 新进程/次(持久版为 PTY/agent) | 新 PTY/次 | 新进程/次 | 新进程树/次 | 新进程/次 | 新进程/次 | 新进程/次 |
| shell 选择 | 自动探测($SHELL),模型可传 shell 覆盖 |
配置化(bash/pwsh 分工具) | 系统 shell | ShellSelect:/etc/shells → git-bash → pwsh/cmd | 配置化 shellPath | getShellConfig(同 pi) | $SHELL → 固定 posix 目录 → Windows Git Bash 路径 | unix_shell() / GOOSE_SHELL 覆盖 |
| login shell | -lc(可 login:false 关) |
否 | PTY 默认行为 | 否 | 否(config 可配) | 否 | 默认 login,snapshot 存在时退化 | 否 |
| OS 沙箱 | seatbelt / landlock+bwrap / win restricted-token | bwrap→landlock / seatbelt / windows-acl,fail-closed | seatbelt / bwrap+seccomp / win helper exe | 无 | 无(BashOperations 可远程委派) | SRT:seatbelt / srt-win(WFP) / bwrap+seccomp | 已撤除(字段保留,仅遥测) | 无 |
| 沙箱文件模式 | read-only / workspace-write / danger | read-only / workspace-write / danger-full-access | workspace root + 写白名单 + 全局只读目录 | —(命令层权限替代) | —(权限层替代) | read_only / workspace_write / delete_guard / full_access | —(已撤除) | —(权限层替代) |
| 网络控制 | 网络策略 + proxy + 按域规则 | 沙箱内网络按 mode | network allowlist + MITM proxy | — | — | deny / allow_all + 凭证屏蔽 | — | — |
| 审批模型 | AskForApproval 4 态 + execpolicy 前缀规则 + Guardian LLM 审查 + 危险命令检测 | 默认模式 + sandbox_permissions 升级(强制 justification)+ ctx.approval | TOML 模式(plan/default/accepting_edits)+ approvedTools + 命令 allowlist | 通配符规则集(默认 ask)+ scanner 精确到子命令 + 只读自动放行 | 无内建审批(宿主可插) | AST 静态管线:fast-allow 白名单 + slow-scan + 危险模式硬拒 + LLM 复核 | 权限服务(只读自动放行、命令规则)+ prepareApproval 钩子(单向收窄) | 无内建审批 |
| 命令解析 | tree-sitter bash → Read/ListFiles/Search/Unknown(用于遥测与权限收窄) | 无(整体 bash -c) | stripShellWrapper + parseCommandDetails(首词 + 子命令) | 自研 ShellParse scanner(posix/bash/powershell)+ ARITY 表 + opaque 检测 | 无 | bash-ast + 命令拆分 + heredoc 剥离 + wrapper 展开 | unbash AST → 逐段 readonly 判定 | 无(首词级 isDangerousCommand 无) |
| 持久化 shell 状态 | 进程池(≤64)+ write_stdin 交互;shell snapshot 回放 | 持久 PTY(每 agent 一个,300s/命令,16k 字符) | 无状态;后台进程表 + PID | 无状态;shellID 输出文件保留 7 天 | 无状态;输出临时文件 | 无状态;后台任务化 | 无状态;后台生命周期(显式/超时自动) | 无状态(drain 500ms 例外) |
| 后台/长任务 | yield_time_ms(默认 10s)+ 后台终态 5min + 流式输出 | run_in_background + job registry;promoteOnTimeout | is_background + 专属工具 list/read_background_output + 日志文件 | background 参数 + jobs + 完成合成消息(禁止轮询提示) | 无(超时即终止进程树) | soft-yield 60s 转 task_id;显式后台 | 显式 + auto_on_timeout(sleep 除外) | 无(前台阻塞至 drain) |
| 默认超时 | yield 10s / 后台 300s | 120s(上限 600s)/ 持久 300s | 非活跃 300s(有输出即重置) | 前台 120s / 后台无超时 | 无默认(模型必选) | 120s / 300s / 3600s 三模式 | 120s(上限 600s) | 300s |
| 输出截断 | 10k tokens / 1 MiB 硬上限 | 64 KB/流 + 64 MiB spill 文件 | 直播 100k 字符 + 缓冲 16 MB | 2000 行 / 50 KiB,尾部保留 + 文件路径 | 2000 行 / 50 KB,尾部保留 + 临时文件路径 | head_tail 预览 + 完整落盘 | inline 30 KB(头尾保留)+ 落盘 5 GiB 软上限 | 2000 行 / 50 KB → 预览 50 行 / 10 KB + 临时文件 |
| PTY 使用 | 是(unified exec 核心) | 仅持久 bash 用终端 seam | 是(node-pty) | 是(daemon 持久 PTY,供实验终端 API) | 否(stdio 管道) | 否(stdio 管道) | 是(node adapter spawn) | 否(stdio + 标签流) |
| 跨平台处理 | 三平台原生沙箱 + PowerShell/cmd 派生 | 三平台 runner 链 + pwsh-sandbox 包 | 三平台沙箱 manager | ShellSelect 平台分支 | cross-spawn + powershell 工具 | 同 pi(继承) | Git Bash 路径 + cmd/pwsh dialect | GOOSE_SHELL + cmd 多行拒绝 |
unified_exec 工具族:exec_command、write_stdin、terminal_session。
参数包含 cmd / shell / login / tty / yield_time_ms(默认10s) / timeout_ms / max_output_tokens / sandbox_permissions / prefix_rule。shell -lc "<cmd>" 新进程;但维护 ≤64 个交互进程池(MAX_UNIFIED_EXEC_PROCESSES),
模型用 write_stdin 与进程交互,实现"伪持久会话"。shell-command/src/shell_snapshot.rs)。AskForApproval 四态(UnlessTrusted / OnRequest / Granular / Never)+ execpolicy 前缀规则
(PrefixPattern,支持 alt)+ 危险命令检测(rm -f、sudo 递归、env、trap、Windows 专属规则)+ Guardian LLM 审查。Read / ListFiles / Search / Unknown 四类,
用于遥测分类(read/list_files/search/unknown/mixed)与只读权限收窄;解析失败折叠为 Unknown。ShellExecutor):调用方拿到进程句柄,前台/后台是调用方语义,不是进程属性。
同一句柄可被 result()(前台投影)或保留(后台 producer)。tool-bash 注入 ['shell'](本执行器),
tool-bash-persistent 注入 ['terminals'](PTY 终端接缝)。两者互斥挂载,模型任一宿主只看到一个 shell 工具。bash-local(新进程/次,默认超时 120s/上限 600s,输出 64 KB + 64 MiB spill)、
bash-sandbox(包装同一 argv 过 ctx.sandbox)、tool-bash-persistent(每 agent 一个长驻 PTY,300s/命令,16k 字符截断,超时/退出即 reset)。SandboxMode 三档;平台 runner 链 Linux [bwrap, landlock] / macOS [seatbelt] / Windows [windows-acl];
选链按平台优先、探测仲裁;不可用即 fail-closed(前台抛 SANDBOX_UNAVAILABLE)。sandbox_permissions 必须配对非空 justification;经 ctx.approval 通道 fail-closed 审批;
升级词汇表 ['workspace-write','danger-full-access'];拒绝时给出同轮 escalation hint。run_in_background;promoteOnTimeout——前台命令超时后转后台而非杀死;
onExpiry:'none'|'kill' 由调用方决定到期语义。DSH_* 快照(类型化托管键,调用方无法覆盖)。XXX(stateful-shell) TODO 表明团队仍在权衡持久 shell 的工作流需求。command / dir_path / is_background / delay_ms。GEMINI_CLI_WARNING 提示。list_background_processes 与 read_background_output;后台命令输出落
background-<pid>.log 文件;完成通知截断注入 5000 字符。~/.gemini/policies/sandbox.toml),内置 plan/default/accepting_edits 三模式,
每模式含 network / readonly / approvedTools / allowOverrides。isDangerousCommand 覆盖 ln -s、rm -f、sudo 递归、find -exec、git config 覆盖全局、rg 危险参数。wrapUntrusted 标记防注入。command / workdir / timeout(默认2min) / background;每次 create 起一个进程,
合并 stdout/stderr 写入输出文件,文件保留 7 天。shell/parse.ts):支持 posix/bash/powershell 三文法;
维护 ARITY 表(git:2, docker:2, npm run:3…)识别命令与子命令;提取 resource + save(重定向目标)与涉及目录;
无法分析时返回 opaque——直接报错,绝不降级放行。permission.assert;规则是按 action+resource 双通配符的规则集,
默认 ask;findLast 匹配实现"后配置覆盖先配置"。AGENT=1 / OPENCODE=1 / OPENCODE_SESSION_ID。command / timeout(秒,无默认)。powershell 工具同构(加 UTF-8 输出前缀)。spawn(shell, args, command),detached 进程树;超时/取消走 killProcessTree(先 SIGTERM 后升级)。DEFAULT_MAX_LINES/BYTES),尾部保留;完整输出落临时文件并把路径写进结果。execCommand:默认缓冲上限 min(256 MiB, 字符串上限);超限即杀进程并把退出码改写为 1(截断绝不伪装成功);SIGTERM→5s→SIGKILL。BashOperations 接口允许宿主替换执行面(远程 SSH 即通过此实现)。command / description / timeout / run_in_background;描述明确"优先专用工具、cd 不跨调用持久"。agent-modules/permission/src/tools/):bash AST → 命令拆分 → heredoc 体剥离 → wrapper 展开 →
fast-allow 首词白名单(~100 个只读/构建命令)→ slow-command 扫描 → 危险模式硬拒(curl 管道执行等)→ 命中即降级 ASK。rm 经 mavis-trash -- sentinel 走可恢复删除(Windows 回收站/宿主回收站);
禁止用绝对路径绕过;SIGKILL 级 kill -9 从 fast-allow 降级。command / timeout / run_in_background / dangerouslyDisableSandbox。classifyCommand 输出遥测类别(test/package/git/build/search/network);
isRuntimeReadOnlyBashCommand 逐段判定只读(含 git 子命令语义、写入选项、重定向)——只读命令自动放行。riskLevel:"high", needsApproval:true, denyPriority:"beforeAsk";
命令注册表(HIGH_RISK_ROOT_COMMANDS: bash/chmod/dd/mkfs/rm…)+ wrapper 选项展开(sudo/env/time)+ docker 规则;
prepareApproval 钩子——只能把 ask 收窄为 proceed,反向不允许;预览失败时 fail-open 为纯询问(不静默放行)。run_in_background 与 auto_on_timeout(超时自动转后台;sleep 除外);输出落盘 5 GiB 软上限。isModelContentBounded 保证模型侧有界);落盘策略按前台/后台区分。ExecutionRequest.sandbox 字段保留但无消费方;
代码注释明确"准备阶段只剩 shell snapshot"(node-execution-adapter-run.ts:399);
dangerouslyDisableSandbox 仅影响遥测 sandboxed 标记。隔离责任转移到权限规则 + cwd 策略(decideBashCwdPolicy)。decideBashCwdPolicy 把 cwd 当策略面管理。command / timeout_secs(默认300s);描述由 shell 类型动态生成(cmd 时提示多行问题)。tokio::process::Command,shell -c;Linux 上 prctl(PR_SET_PDEATHSIG) 防孤儿进程。truncate_preview_bytes 防 \r 进度条伪造无换行长行绕过预览上限。OUTPUT_DRAIN_TIMEOUT)——兼容"后台化子进程继承 stdio"的场景,drain 失败仅标记 output_truncated。flatpak-spawn --host 并显式传递宿主 login PATH。! 前缀 kickoff 消息直接作为 shell 命令执行(ops_bang_shell)——人类敲命令的快速通道。safe.bareRepository=explicit + core.fsmonitor=false(防配置注入与 fsmonitor hook)。\r 绕过预览的防御说明:截断实现必须假设"输出可能是对抗性的"。本仓库仅为 Kiro 的 issue 追踪与自动化脚本(label 分类、重复检测、stale 管理),不含 harness 源码。 官方资料称 Web 端在多仓库任务中使用 isolated cloud sandboxes(云端隔离沙箱), IDE/CLI/Crew 共用"一个统一 agent harness"。设计细节无法从源码核验,本报告不对其内部机制做任何断言。
| 路线 | 实现 | 收益 | 风险 |
|---|---|---|---|
| 无状态 + snapshot 回放 | codex shell snapshot | 隔离不破;aliases/函数/env 延续;凭证安全处理后单独注入 | snapshot 本身是攻击面(eval 内容来自用户 rc);只捕获声明式状态,cd 目录不跨调用 |
| 持久 PTY 会话 | dsh tool-bash-persistent | cwd/env/函数全连续;终端语义完整(交互程序) | reset 前状态可被后续任意命令读取(跨命令信息泄漏);超时即 reset 的语义突变;PTY 缓冲管理复杂(scrollback 分页) |
| 显式状态参数 | opencode/gemini workdir + 后台进程表 |
模型完全掌控状态;无隐式泄漏面 | 模型负担重(必须自己记得 cwd);cd 在单命令内生效的约束靠提示词维持 |
workdir 参数应作为所有路线的标配(防止 cd 进权限判定盲区——mcode/ZCode 的工具描述均显式警告 cd 不跨调用持久)。
"管道"在本报告含两层:shell 语法管道(|、&&、;)与数据的stdio流转。8 个 harness 对前者的处理分三档:
| 档位 | 策略 | 代表 | 分析粒度 |
|---|---|---|---|
| 不分析 | bash -c 整串执行,权限看首词或整体 | goose、pi、dsh bash-local、gemini | 零解析成本;cat a | rm -rf / 这类组合无法拆分判定 |
| 解析但仅用于放行 | 解析失败/含动态词 → 整体询问,绝不整体放行 | opencode(opaque 即报错)、ZCode(parseErrors 即 matcher 全匹配)、mcode(AST 拆分逐段) | 只读段免打扰,写入段逐段判定;解析器成为攻击面 |
| 解析 + 内核沙箱双保险 | 命令分析做权限收窄,OS 沙箱做最终边界 | codex(ParsedCommand 四分类 + sandbox 重试)、dsh bash-sandbox、mcode | 解析错一次不致命——内核兜底 |
alias)、
eval/$()/反引号命令替换、heredoc 体(mcode 专门剥离)、bash <script> 文件参数、
glob 与变量展开后的动态词。所有 harness 的共识防线是:解析只用于"放行",不用于"拦截"。
| 级别 | 实现 | 适用面 | 8 个 harness 的分布 |
|---|---|---|---|
| 内核级(三平台原生) | seatbelt / bwrap+seccomp / landlock / Windows restricted token·WFP·ACL | 服务端、多租户、CI;本机高风险 | codex、dsh、gemini、mcode(4/8 主力) |
| 命令语义级(无 OS 沙箱) | AST 分析 + 只读放行 + 危险模式硬拒 | 单租户本机、模型可信度高 | pi、goose、opencode、ZCode(4/8,其中 ZCode 由内核级主动撤除至此) |
| 外部委派 | 容器/VM/远程(Kiro 云端、opencode workerd 变体等) | 多仓库任务、完全隔离 | Kiro(宣称),pi 经 BashOperations 接缝 |
值得注意的数据点:
| 档位 | 机制 | 打扰率 | 自主上限 | 代表 |
|---|---|---|---|---|
| 逐次询问 | 每次执行都问 | 100% | 零 | —(无 harness 采用为默认) |
| 只读自动放行 | 逐段/首词 AST 判定只读即放行 | <20% | 只读操作全自主 | ZCode、mcode、opencode、codex(ParsedCommand::Read 收窄) |
| 会话级 allowlist | always-allow 模式;按命令规则记忆 | 逐会话收敛 | 写入操作经一次授权后全自主 | dsh escalation(强制 justification)、ZCode alwaysAllowPatternSources、codex prefix_rule |
| 全局模式 | plan / default / accepting_edits / danger 模式切换 | 模式内零打扰 | 整个模式授权(如 accepting_edits 白名单 sed/awk/perl) | gemini(TOML 模式)、dsh(三档 SandboxMode) |
validateEscalationArgs),给审计留痕;
②prepareApproval 类钩子只能收窄不能扩权(ZCode),预览失败 fail-open 为纯询问。
| 参数 | 收敛值 | 分歧点 |
|---|---|---|
| 前台超时 | 120–300 s | gemini 非活跃超时(输出即重置)vs pi/mcode 无默认或三模式;dsh 的 promoteOnTimeout(超时转后台不杀) |
| 输出截断 | 2000 行 / 50 KB(pi/opencode/goose 完全一致,疑似同源) | codex 10k tokens;ZCode 30 KB 字符级头尾;gemini 16 MB 缓冲(最宽松) |
| 完整输出 | 一律落临时文件 + 路径回报 | dsh spill 64 MiB 上限;ZCode 5 GiB 软上限;opencode 7 天保留 |
| 后台通知 | 完成自动通知,禁止轮询 | opencode 显式写入工具描述;gemini 截断注入 5000 字符 |
truncate_preview_bytes 注释点明:无换行的 \r 进度条、minified/Base64 内容
可以按行截断"合法"地走私无限长预览——截断实现必须字节封顶。
pi 的 exec.ts 更进一步:截断时把退出码改写为 1,截断绝不伪装成功。
| # | 决策点 | 建议 | 依据 |
|---|---|---|---|
| 1 | 沙箱与否 | 服务端/多租户:内核级沙箱(bwrap/seatbelt 平台链 + fail-closed)。单租户桌面:可撤除,权限层兜底,但保留接缝字段(ZCode 教训:字段保留、消费方可插拔) | dsh runner 链 + fail-closed;ZCode 撤除注释 |
| 2 | 持久化模型 | 默认无状态 + workdir 参数;持久 PTY 作为可选工具(带 reset 语义与滚动分页);高连续需求场景用 snapshot 回放 |
dsh 双工具并存;codex snapshot;mcode 工具描述显式警告 cd 不持久 |
| 3 | 管道策略 | 整串 bash -c 执行;AST 解析仅用于"放行":解析失败/含动态词 → 整体询问。绝不用解析结果做整体拦截 |
opencode opaque 报错;ZCode matcher 全匹配;mcode heredoc 剥离 |
| 4 | 审批分层 | 只读自动放行(逐段判定)→ 写入会话 allowlist(强制 justification)→ 全局模式兜底;升级路径 fail-closed | dsh escalation;ZCode prepareApproval 单向收窄;gemini TOML 模式 |
| 5 | 超时策略 | 前台硬超时 120–300s;提供 promoteOnTimeout 选项(长任务转后台而非杀死);非活跃超时作为可选模式 |
dsh promoteOnTimeout;gemini 非活跃超时;mcode soft-yield |
| 6 | 输出截断 | 2000 行 / 50 KB 头尾保留 + 完整输出落盘 + 路径回报;截断必须字节封顶(防 \r 走私);截断改写退出码为 1 |
pi/opencode/goose 收敛值;goose truncate_preview_bytes;pi exec.ts |
| 7 | 后台化 | 显式 run_in_background + 超时自动后台(sleep 类除外)+ 完成自动通知 + 禁止轮询提示 |
ZCode auto_on_timeout;opencode 通知文案;gemini 日志落盘工具 |
| 8 | 执行接缝 | shell 执行抽象为可替换服务(本地/远程/沙箱三实现);环境消毒(凭证 scrub → 用户 env → 托管 DSH_*)顺序固定 | dsh ShellExecutor 接缝;pi BashOperations;dsh 环境合并顺序注释 |
Shell 工具的设计顺序应当是:先定租户模型(单租户桌面 vs 多租户服务端),再定沙箱档位; 权限层一律"解析只放行、不拦截";持久状态宁缺毋滥,必须持久则显式 reset; 截断必须诚实(字节封顶 + 退出码改写);超时必须留逃生口(转后台而非杀死)。