给编码 Agent 建立安全边界:Pi 的三种沙箱方案
约 2192 字大约 7 分钟
PiAI AgentDockerSecurity
2026-09-05
Pi 默认以 YOLO 模式运行:不对命令预审,也不弹出权限确认。它换来了连贯的自动化体验,但文件系统、Shell 和网络权限也随之交给了 Agent。
问题不在于“要不要弹窗”,而在于安全边界放在哪里。只要 Agent 能读取敏感数据、修改文件并执行任意代码,单靠应用层的白名单、黑名单和确认框都容易被脚本、符号链接或间接执行绕开。更可靠的做法是让 OS 级隔离承担最后边界。
本文根据 《第十二章:容器化与安全沙箱》 整理,并以 Pi 的实际使用场景重新组织。
安全边界应分成三层
安全不是一项配置,而是纵深防御:
| 层次 | 目标 | 典型手段 |
|---|---|---|
| 基础设施层 | 限定 Agent 实际能触及的资源 | VM、Docker、namespace、cgroup、seccomp |
| 配置层 | 缩小工具与网络权限 | 工具白名单、只读挂载、出站限制 |
| 运行层 | 让协作过程可预期、可审计 | AGENTS.md、审查 diff、最小授权习惯 |
其中第一层最关键。提示词能约束正常行为,不能替代不可绕过的文件、网络和进程隔离。
一次工具调用实际经过哪些组件
以“安装依赖后修改 src/server.ts”为例,模型不会直接获得主机 Shell。它先产生工具调用,Pi 再根据所选沙箱把请求转发到相应执行环境;执行结果才会回到模型上下文。
Pi 主进程负责模型通信、会话和工具路由;沙箱负责命令、文件与网络的实际访问。安全设计的关键,是让后者看不到不该暴露的主机资源。
Gondolin:把工具执行放进微虚拟机
Gondolin 的思路是“主机保留大脑,微 VM 承担手脚”:Pi 进程继续在主机运行,认证凭据也留在主机;bash、read、write、edit 等工具调用则被转发到独立 Linux 微虚拟机。
它的优势是凭据不会进入工具执行环境。即使模型被 prompt injection 影响,或某个依赖的脚本存在恶意行为,影响面也被收敛到 VM 内;VM 可随会话销毁重建。
推荐配置的重点:
- 仅挂载当前项目目录,不挂载主目录与凭据目录。
- 默认关闭网络;需要安装依赖时,仅放行必要的出站端口。
- 限制每个 VM 的 CPU、内存与超时。
- 审查场景使用只读工具集,并启用临时 VM。
Gondolin 的代价是需要 Linux 和 KVM 环境,适合对本地凭据隔离要求最高的开发工作流。
示例:恶意依赖脚本的可见范围发生了什么变化
假设 Agent 执行 npm install some-package,其中的 postinstall 试图读取 ~/.ssh/id_ed25519 并上传。
| 环境 | 脚本看到的 ~/.ssh | 能否联网 | 结果 |
|---|---|---|---|
| 主机直接运行 | 主机真实目录 | 通常可以 | 私钥可能被读取并外传 |
| Gondolin:未挂载主目录、网络关闭 | VM 内不存在该文件 | 不可以 | 读取和外传均失败 |
| Gondolin:只放行 HTTPS | VM 内不存在该文件 | 仅受限出站 | 仍无法获得主机私钥 |
这里的变化不在于 Agent 是否“遵守规则”,而在于工具执行环境里根本没有主机私钥可读。
Docker:更通用,但凭据隔离较弱
Docker 方案把 Pi 进程和工具执行都放进同一个容器。它更适合 macOS、Windows、CI,或团队希望用同一镜像统一开发环境的场景。
容器不是默认安全。一个可用的最小基线应包括:
docker run -it --rm \
--user 1000:1000 \
--cap-drop ALL \
--security-opt no-new-privileges \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=256M \
-v "$(pwd):/workspace:rw" \
-w /workspace \
--network none \
--cpus 2 --memory 1g --pids-limit 50 \
pi-agent这组限制分别减少 Linux capabilities、阻止权限提升、固定根文件系统、隔离临时目录,并限制网络、CPU、内存和进程数量。运行镜像时还应使用非 root 用户;安装依赖可使用 npm install --ignore-scripts,避免生命周期脚本在构建阶段执行。
Docker 的核心取舍是凭据:Agent 要调用云端模型,API Key 通常必须以环境变量或只读挂载形式进入容器。因此它能保护主机的大部分资源,却不能阻止已被操纵的 Agent 读取容器内凭据。敏感项目更适合 Gondolin,或为容器使用短期、低权限凭据。
示例:同一工作区在 Docker 中如何变化
/workspace 的改动会回写到主机项目,所以 git diff 仍能看到 Agent 的编辑。相反,未挂载的 ~/.ssh、~/.aws 和 /var/run/docker.sock 在容器内不可见;Docker Socket 必须特别避免挂载,它等同于把控制主机 Docker 的权限交给容器。
OpenShell:按路径和网络声明权限
OpenShell 位于两者之间:它不启动 VM 或容器,而是通过 Linux 的 seccomp、Landlock 等机制,或 macOS Sandbox,为进程套上可声明的策略。
它适合需要精细规则的场景,例如 Agent 可以读整个项目,但只能写 src/;可以访问模型 API,却不能访问任意公网地址。
default_action: deny
filesystem:
read:
- path: "./"
recursive: true
write:
- path: "./src/"
recursive: true
deny:
- path: "./.env"
- path: "./*.pem"
- path: "~/.ssh/"
network:
allow:
- host: "api.openai.com"
port: 443策略应从 deny 开始,再按任务增加最小权限。尤其应显式排除 .env、私钥、云凭据、SSH 配置和 Docker Socket;后者一旦挂载进容器,Agent 可以借助 Docker 创建特权容器,隔离形同虚设。
示例:把“可写项目”收窄为“只可改源码”
当任务只是修复 TypeScript 逻辑时,Docker 和 VM 都能限制挂载范围,但 OpenShell 可以继续细分到目录和文件规则:
| 操作 | default_action: deny 下的结果 | 原因 |
|---|---|---|
读取 src/app.ts | 允许 | 项目目录在 read 规则内 |
写入 src/app.ts | 允许 | src/ 在 write 规则内 |
修改 .env | 拒绝 | 命中显式 deny |
新建 scripts/release.sh | 拒绝 | scripts/ 不在写入范围 |
请求 api.openai.com:443 | 允许 | 命中网络白名单 |
| 请求任意下载站 | 拒绝 | 未命中网络白名单 |
这使权限能够随任务变化:修复源代码时只开放 src/;需要升级依赖时,才临时增加 package.json 与锁文件的写权限。
按任务选择隔离等级
| 场景 | 优先方案 | 网络建议 |
|---|---|---|
| 只读审查、生成文档 | Gondolin 或 Docker | 关闭网络 |
| 本地模型辅助开发 | Docker 或 OpenShell | 仅连本地服务 |
| 需要云端模型与包管理 | Gondolin 或受限 Docker | 仅允许必需的 HTTPS 出站 |
| 有生产凭据、私有数据的项目 | Gondolin | 凭据不进入工具环境 |
| CI 中批量执行任务 | Docker | 临时凭据、最小挂载 |
隔离并不消除审查责任。每次让 Agent 执行前,仍应明确工作目录、授权的目标文件和网络需求;每次执行后,用 git diff 审查改动。沙箱负责限制最坏后果,人负责确认任务本身值得被授权。
