与其问“是否允许”,不如先设好边界
Agent 写代码,"读文件、改代码、跑命令" 是 Coding Agent 完成任务的一部分。对开发者来说,Agent 能干的事情越多,我们越不想被每一条命令打断,也越需要知道它不会越界,不会动到不该动的文件和数据。
这套能力不是凭空想的。沙箱上线以来,每天有近百万条终端命令从它面前走过,上万条会被标记为可疑,其中近百条直接动到了不该动的地方。这足以让一个开发者一周的工作付之东流。
而这些命令不一定来自恶意操作。模型幻觉可能把项目目录当成临时目录删除,环境变量差异可能让清理命令指向系统目录。
这也是 Qoder 做原生「终端沙箱」的原因:当 Agent 可以替你执行命令,不能只问一句「是否允许」。更好的做法,是先替开发者画好边界,再让 Agent 在边界内执行。
真正出事,往往在命令执行那一刻
下面两个案例,分别对应模型幻觉和第三方工具风险。
案例一:一条清理命令,清空了 C 盘
截图里,Agent 正在执行一条 Windows 命令:
cmd /c "rmdir /s /q \"C:\Users\...\SuperMarioGame\src\""
它本来只是想清理文件。模型却把项目源码目录当成了可删除目录。
rmdir /s /q 会递归删除目标路径,而且不会二次确认。
这类问题麻烦就麻烦在:命令看起来像正常开发操作,没有明显的恶意 payload,结果却可能无法恢复。
案例二:环境变量展开,清理命令指向了系统目录
开发者让 Agent 执行一段构建清理脚本,命令里有一行:
rm -rf "$OUTPUT/root"
本地环境里,$OUTPUT 指向 /home/user/project/build,删的是构建产物。但 Agent 执行时环境变量未正确加载,$OUTPUT 展开为空,命令变成了 rm -rf "/root",直接指向系统库目录。
这类问题不需要攻击者介入,纯粹是运行环境差异。沙箱把写入权限限制在工作区内,/root 不在白名单里,删除被直接拒绝。
模型幻觉、环境变量差异,来源不同,结果一样:命令的实际行为和开发者预期不一致。执行环境里任何一环失控,最后都可能落到用户机器上。
黑名单和弹窗为什么不够
行业里常见的终端安全防护,主要有两类:黑名单和确认弹窗。
黑名单拦截 会把 rm -rf、sudo、format 这类高危命令列进清单,匹配到就拦。问题是,字符串级匹配很容易绕过。cmd /c "rmdir /s /q" 和 rm -rf 干的是同一件事,但写法完全不同。反引号嵌套、管道拼接、变量展开,都会让规则变得脆弱。更隐蔽的做法是把危险命令写进 .sh 脚本再执行,命令名根本不会出现在调用参数里。
用户确认弹窗 看起来更稳妥。每条命令弹出确认框,让用户决定是否放行。但在真实开发环境里,命令太多、上下文太碎,很多人最后会直接点「允许」。弹窗没有消失,安全责任反倒推给了用户。
更关键的是:命令一旦被放行,它和你手动敲的命令权限一样,文件、网络、环境变量、系统资源都能访问。
终端安全需要多一步:「进程隔离」。风险命令可以运行,但不能随便读写你的机器。
行业怎么做的
我们调研了几类主流 AI 编程工具的终端安全方案。站在开发者视角看,终端执行已经是标配,沙箱也在快速补齐。但很多方案还需要用户理解不少底层差异:哪些平台支持、网络能不能访问、哪些路径可写、失败后怎么放行。
Cursor 的 Agent 可以直接在终端里运行 shell 命令,也支持沙箱执行。它通过 sandbox.json 配置网络、文件系统路径和 allowlist。macOS 和 Linux 侧有更直接的沙箱能力,Windows 侧主要通过 WSL2 获得隔离环境。对已经习惯 WSL 的开发者来说,这条路径可用;对希望直接在 Windows 原生终端里工作的用户来说,仍然多了一层环境选择。
Codex 把终端安全做得更偏命令行和执行策略。公开资料里,Codex CLI / IDE Extension 会通过 OS-level 机制执行 sandbox policy,限制模型生成命令的文件写入范围、网络访问和审批策略。它把 policy 暴露得比较完整,适合 power user 和自动化场景。
Claude Code 的思路是用沙箱减少频繁权限提示,让 Bash 命令在文件系统和网络边界内更自主地执行。公开资料里,macOS 侧基于 Seatbelt,Linux 侧基于 bubblewrap。它看重的是「少打断」和「边界内执行」的平衡,适合长任务和自动化执行。
调研下来可以看到,只靠权限弹窗不够,Agent 需要更明确的本地执行边界。直接影响体验的,是用户正在使用的平台有没有覆盖,是否需要额外切换到 WSL / 容器环境,以及沙箱失败后能不能顺畅回到用户确认流程。
所以 Qoder 在 macOS、Linux、Windows 三个平台分别接入了最适合的底层机制。Agent 在真实开发环境里自动执行时,文件系统和网络都有边界。用户打开 IDE 就有沙箱保护,不用去理解每个平台底下是怎么实现的。

一条命令被执行前,会发生什么
一条命令执行前,会先走过这条链路:风险识别、命令解析、沙箱包裹、平台隔离。
先判断哪些命令有风险
第一层:LLM 风险判定。 模型在生成命令时先判断风险。这一层看的是命令意图,比如"这条命令在删除系统路径""变量值不确定,执行结果不可预测"。
第二层:多平台命令解析器。 三套 AST 解析器分别处理 PowerShell、Bash/Zsh、CMD 的命令语法,把原始命令拆成结构化的命令名和参数列表。
举个例子。Prompt 注入让模型生成了这样一条命令:
whoami `rm -rf /`
肉眼看是一条 whoami。解析器拆出来的结果是 { cmd: "whoami", args: ["rm -rf /"] },提取到两个命令名:whoami 和 rm。反引号里藏着的 rm -rf / 被识别出来了。
这和前面提到的字符串级黑名单不一样:先做 AST 级解析,再比对名单,变形和嵌套绕不过去。
第三层:内置危险命令名单。 解析出的命令名逐一比对内置名单,rm、format、del、rmdir 等会被标记。LLM 判定有风险,或命令解析器在结构分析中发现隐藏的危险命令,会直接进沙箱执行。如果仅因命令名匹配到内置名单(比如命令里包含 rm),系统会弹出确认框,由用户决定是否放行。

多看一眼:两层 AI Review
上面这条链路只回答了一个问题——「这条命令危不危险」,却没回答真正影响体验的那个问题:「危险了之后该怎么处置」。被标危险的命令不一定就是破坏性的。删掉一个已被 git 跟踪、而且已经提交的文件,完全可以恢复;删掉一个由未展开变量拼出来的路径,则无法恢复。两者一视同仁,结果要么是确认框泛滥,要么是风险被悄悄放过。光靠命令名匹配,分不出这两者的差别。
所以我们近期上线了 AI Review:在命令被判危险之后,再用一个轻量小模型做一次同步的语义判断。它和第一层的生成时风险标记不是一回事——第一层判的是「意图」,发生在生成命令时;这一层判的是「后果」,发生在即将执行的那一刻。而且它会在两个不同的节点各做一次,分别守住两条不同的边界。
第一次 review 守在沙箱入口。当一条被标危险的命令首次准备执行,这次 review 判断它会不会大范围删除或覆盖工作区内的文件。为了判断「删了能不能恢复」而不只是「想干什么」,我们把仓库的 git status 作为自然语言上下文喂给模型——这个目录有没有版本控制、有多少未提交的文件。判定安全就进沙箱执行,不安全就回退到确认框。这一次 review 保护的是当前工程目录内的数据。
第二次 review 守在权限升级这道关。当沙箱执行失败、模型请求用全量权限在真实终端里重跑,命令就要彻底离开沙箱边界了——所以这次 review 刻意更保守。它只看命令本身和工作区路径,判断命令会不会破坏工作区以外的系统文件。安全才放行升级,否则同样退回确认框。这一次 review 保护的是当前工程目录外的数据。
机制上两次 review 共用同一个轻量小模型,低温度保证输出稳定,返回带 safe(是否安全)和 reason(判断理由)的结构化 JSON。单次调用 10s 超时,最多重试 3 次。兜底策略是刻意做成不对称的:实验开关关闭时,沙箱入口默认放行(维持原行为),权限升级默认阻断;模型完全不可用时,一律阻断。多弹一个确认框是小事,漏放一条破坏性命令不是。

再用「命令包裹」接管执行
确定要进沙箱后,Qoder 会使用「命令包裹」(command wrapping)接管执行:原始命令经过平台对应的沙箱工具包裹后再进入终端运行。用户在终端里看到的是被隔离的进程,看不到包裹层。
Agent → SandboxWrapper.wrapCommand(cmd, policy) → 终端执行
代码里用统一接口 ISandboxWrapper 做抽象,再按操作系统分派到三种实现:

三个平台共享同一套策略。文件系统层面,工作区目录可写,其余目录只读,~/.ssh 等敏感路径对进程不可见。网络层面,默认不限网络,也可以按策略收紧到完全阻断。
macOS:用系统自带的 Seatbelt
macOS 这条路最顺:sandbox-exec 是 Apple 自己用来给系统服务做沙箱的工具,内核级隔离,性能调教过,而且系统自带,不引入任何第三方依赖(对企业用户的合规审查也最友好)。我们直接复用,运行时动态生成 Seatbelt 安全策略文件。策略从 (deny default) 开始,默认拒绝一切,然后白名单放行系统库、IPC 通信等基础调用。文件读写按策略翻译成对应的 allow/deny 规则,网络用粗粒度开关控制。
实际调试过程中我们踩了一些坑。比如 Git 操作在沙箱里需要临时目录(/tmp、/private/tmp)的写权限和设备文件(/dev/null、/dev/urandom)的读写权限,不加这些 git status 都跑不起来。同时我们把 .git 目录设为只读,这样 git log、git diff 这些读操作正常,但 git commit、git push 这些写操作会被拦住,需要用户主动升级权限。
Linux:用 bubblewrap 构建轻量隔离
Linux 用 bubblewrap,基于内核的 user namespace 构建轻量隔离环境。整个根目录 --ro-bind 只读挂载,需要写入的路径用 --bind 覆盖,敏感路径用 --tmpfs 挂空目录覆盖。网络隔离用 --unshare-net 一行搞定。
首次使用时会检测 bwrap 是否安装,缺失时弹窗引导一键安装(自动识别 apt / dnf / yum / pacman / zypper)。如果用户不装,降级为非沙箱执行并给出提示。
Windows:自研 sandbox.exe
Windows 上没有类似 sandbox-exec 或 bwrap 的现成方案,这也是整个项目里最难的一段。
我们调研了 15 种隔离技术,从 Hyper-V 虚拟机、Windows Sandbox、WSL2,到 AppContainer、Sandboxie、Job Object、Restricted Token、Mandatory Integrity Control,再到 AppLocker、WDAC、Windows Defender Application Guard、WASM 运行时、Docker Desktop、防火墙规则、Process Mitigation Policies,覆盖了从虚拟机、容器到用户态隔离的全谱。每一种都有适合自己的场景,但放进 IDE 这个使用频率高、启动延迟敏感、还要兼容老版本 Windows 的环境,能同时满足这几条约束的并不多。
最终我们基于 Windows 系统自带的安全原语自己组装了一套,用 Rust 实现、随 IDE 分发,在 Windows 7 之后的所有版本上都能用,毫秒级启动。主要靠三件事:
- 受限令牌:
CreateRestrictedToken创建弱权限令牌,子进程只能向显式授权的路径写入。 - DACL 动态授权:为工作区路径分配唯一的 Capability SID,运行前向 ACL 注入写入权限,进程退出后立即撤销(只读模式下)。
- Job Object:托管进程生命周期,保证超时或异常退出可控。
为什么不用 AppContainer?它的隔离确实更强(默认拒绝一切,注册表重定向,子进程继承沙箱),但 Capability 系统的动态授权难度大,而且只兼容 Windows 8 以上。受限令牌方案兼容性更好,启动更快,资源访问粒度也更灵活。
Windows 的 Shell 多样性也是一个挑战。CMD、PowerShell、Git Bash 的引号规约完全不同。针对 PowerShell,我们用 -EncodedCommand(UTF-16LE + Base64)来规避内层引号冲突。
安全提升,但不能牺牲用户体验
沙箱有一个现实问题:它可能拦住正常操作。
沙箱执行失败时,系统把失败原因反馈给模型,由模型判断是否需要升级权限、在真实终端中重新执行。用户确认后才会在正式终端执行。
在 Experts 专家团模式下,潜在危险命令自动在沙箱中执行,不需要用户逐条确认。
执行速度也不能拖后腿。macOS 上,sandbox-exec 的一次性启动开销稳定在 43-72 毫秒,对秒级命令的影响不到 5%,命令输出和非沙箱执行完全一致。
真实工作负载里,越长的命令开销越接近 0:tar 压缩 +3%、seq 排序 +0.3%、find 全盘 +2%、Shell 循环 +1%。开销主要集中在 echo / ls 这类毫秒级命令上:echo 看着 +270% 很吓人,但绝对值是 +54ms,对实际开发流程没感觉。
Windows 上,两轮综合测试平均开销 +7.7%。纯循环和 IO 密集型命令几乎没有额外开销,主要影响在进程启动环节,例如 PowerShell spawn 约 +19%,每次约多 4.8 秒。

每天拦截了什么
回到开头的数据。以一天为例:

高风险特征:删除目标指向系统路径、用户主目录、未知变量或通配符,常带 sudo 或远程执行,不可恢复。
中风险特征:删除目标是相对路径或项目目录,但缺少 cd 前置,工作目录错误时可能误删非预期文件。比如 rm -rf comic-site,没有确认当前目录。
每天近 100 个可能出问题的命令,沙箱在后台挡住。
数字背后:当 HOME
数字是冷的,下面这个真实的开发者反馈,是我们一直挂在墙上的提醒。
他让 AI 帮忙清理项目根目录里的临时文件,项目目录下恰好有一个名为 $HOME 的子文件夹。模型生成了这条命令:
rm -rf "$HOME"
肉眼看是项目内删除。可命令进入 shell 那一刻,$HOME 被展开成了用户的真实主目录路径,递归删除发生在主目录上。当时弹窗确实弹了,开发者也按了确认:他在脑海里想的是项目里那个 $HOME 文件夹。两边语义不一致,确认按钮无法弥合。最后磁盘几乎被清空,操作系统都跑不起来了。
如果命令在沙箱里执行,结局完全不一样:工作区目录可写,其余路径只读,$HOME 展开后落在工作区之外,删除会被直接拒绝。开发者拿到的是一条"沙箱阻止了项目外删除"的提示,可以再决定是否切到原生终端重试。不用等到重装系统的时候才意识到出了什么事。
弹窗保护的是用户的判断力,沙箱保护的是判断错的那一刻不会失去整个磁盘。
当前限制和后续计划
几个已知限制:
- 域名级网络过滤 还没做。目前网络控制是粗粒度的开/关,精细到域名级的过滤需要引入本地代理层,已在规划中。
- Linux 需要安装 bubblewrap。容器环境下命名空间嵌套受限,目前只做文件系统和网络隔离,不做 PID 隔离。
- Windows 沙箱是用户态机制。挡不住有提权能力的恶意代码。目标场景是防住误伤性写入和意外外联,不能完全覆盖对抗性攻击。
后续我们还会接入:用户自定义策略覆盖、企业策略下发、按命令动态策略、审计日志上报。
我们的判断
开发者用 AI 编程产品,是希望把更多精力放在判断方案、审查结果和推进项目上。重复执行、风险识别和权限边界,应该尽量由智能体平台承担。
以前 Agent 主要生成代码,风险多半还停留在代码审查环节。现在 Agent 会运行命令、改文件、调用 Skills、访问网络,IDE 就需要承担更多本地执行安全责任。
终端沙箱会成为 Agent Harness 的基础设施。价值在于把风险关进更小的边界里。对个人开发者来说,就少一次误删整个项目的风险;对团队和企业,这是权限治理、审计和合规的起点。