pi-guard-sandbox 是官方维护的 Pi extension,把 OS 级 Bash 沙箱、项目配置、只读/工作区可写模式、敏感路径保护和危险命令策略放到了一起。核心原则:Agent 的 Bash 应该进 OS 沙箱,而不是靠模型"记得不要做危险操作"。
两层架构:pi-guard-sandbox 与 sandbox-runtime
pi-guard-sandbox 不是重新实现一套沙箱,仓库里带了一份 fork 后的 runtime(extensions/pi-guard/vendor/sandbox-runtime/),两层各司其职:
pi-guard-sandbox
├── /guard init、/guard status 等 Pi 命令
├── .pi/pi-guard.json 配置
├── readonly / workspace-write
├── protectedPaths、sensitiveReadDeny、bashPolicy
└── 把配置交给 sandbox-runtime
sandbox-runtime
├── macOS:生成 Seatbelt profile,调用 sandbox-exec
├── Linux:生成 bubblewrap 参数,使用 bwrap + socat
├── 启动网络代理
└── 把 Agent Bash 放进 OS 沙箱
配置流向:.pi/pi-guard.json → Guard 配置转换 → sandbox-runtime → sandbox-exec(macOS)或 bwrap(Linux)。截至 v0.3.0,runtime 源码有 macOS 和 Linux 分支(macOS 只检查 rg),但项目 README 仍把 macOS 写成"可能可以工作但尚未测试"。
前置条件与依赖
- Pi 0.84.1 或相近版本(
pi --version);pi-guard-sandbox 0.3.0。 - Guard 只在 Pi 的 TUI 模式里初始化和拦截工具;print、JSON、RPC 等非 TUI 模式不应假设自动获得同样的保护。
- Linux/WSL:
sudo apt install bubblewrap socat ripgrep(bwrap进程/文件系统隔离、socat沙箱网络代理桥接、rg文件扫描)。 - macOS:系统自带
sandbox-exec,只需brew install ripgrep。 - 依赖缺失时 Guard 显示
Guard: sandbox-unavailable——这表示沙箱运行时没准备好,不是某条命令正常执行后被策略拒绝。
安装与初始化
mkdir -p ~/scratch/pharmacy-agent && cd ~/scratch/pharmacy-agent && git init
pi install npm:pi-guard-sandbox # 全局安装
pi install -l npm:pi-guard-sandbox # 只安装到当前项目
安装 package 不会自动启用 Guard。第一次启动时若项目里没有配置文件,Guard 处于 uninitialized 状态,不会自动包裹 Agent Bash。在 Pi 的 TUI 中输入:
/guard init
它会在启动 Pi 时所在的项目目录下创建隐藏目录 .pi 并放入 pi-guard.json(例如 /Users/kolya/tmp/metricflow/.pi/pi-guard.json)。然后 /guard status 查看状态。
/guard status 输出解读(节选):
| 字段 | 含义 |
|---|---|
Guard: enabled |
总 Guard 开关已打开 |
Sandbox: enabled |
Agent Bash 会尝试进入 OS 沙箱 |
Mode: workspace-write |
Agent 可写当前工作区,不能随意写工作区外 |
Network: open |
出站网络没有按域名限制 |
DCG binary: missing |
系统找不到 dcg 可执行文件 |
Bash policy: built-in |
DCG 不可用时使用 Guard 自带 bashPolicy |
DCG binary: missing 不等于整个 Guard 失效:只要 Sandbox: enabled,OS 沙箱仍然保护 Bash,危险命令判断回退到 Bash policy: built-in。另外注意:Guard 只保护交互式 TUI 中 Agent 使用的工具,用户自己输入的 !rm -rf build、!!curl ... 不在 Agent Bash 覆盖范围。
模式实验:readonly vs workspace-write
/guard read-only
# 让 Agent 用标准 Bash 执行:printf 'hello\n' > check.txt → 预期失败(工作区不可写)
# 执行:printf 'temporary\n' > /tmp/guard-check.txt && cat ... → 预期成功
v0.3.0 的实现明确把 /tmp 放进可写路径:
const allowWrite = config.mode === "readonly"
? ["/tmp"]
: [workspaceRoot, "/tmp"];
| 位置/工具 | readonly | workspace-write |
|---|---|---|
| Agent Bash 写工作区 | 不允许 | 允许 |
| Agent Bash 写 /tmp | 允许 | 允许 |
| Pi 的 write / edit 工具 | 不允许 | 按路径策略处理 |
| 工作区外 | 不允许或需批准 | 不允许或需批准 |
/guard workspace-write 切回。/tmp 适合临时脚本、缓存和测试输出;要提交到 Git 的文件仍应写进工作区。
配置文件:.pi/pi-guard.json
{
"enabled": true,
"sandbox": { "enabled": true },
"mode": "workspace-write",
"network": "blocked",
"dcg": {
"enabled": true,
"onDeny": "confirm",
"onIndeterminate": "notify",
"onError": "notify"
},
"sensitiveReadDeny": ["~/.ssh", "~/.aws", "~/.npmrc"],
"protectedPaths": {
"block": [".git"],
"approval": [".env", ".env.*", ".pi/pi-guard.json"]
},
"bashPolicy": {
"directBlock": ["sudo", "su", "dd"],
"requireApproval": ["rm-rf", "git-reset-hard", "git-clean-fd"]
}
}
enabled 与 sandbox.enabled
把它们想成总电闸和 OS 防盗门:
| enabled | sandbox.enabled | 效果 |
|---|---|---|
| false | 任意 | Guard 整体关闭 |
| true | false | Guard 的 Pi 工具策略和 Bash policy 仍工作,但 Agent Bash 不进 OS 沙箱 |
| true | true | Guard 策略工作,Agent Bash 也经过 OS 沙箱 |
/guard sandbox off 只关闭 Bash 的 OS 沙箱;/guard off 关闭整个 Guard。关闭 sandbox.enabled 后,Bash 恢复普通宿主机文件和网络权限,不能再依靠 OS 沙箱保护 Bash 读取 ~/.ssh。
最小示例验证:sandbox.enabled=false 时,Agent 调 write 工具写 .env 会经过 protectedPaths.approval 弹确认;但 Bash 执行 cat ~/.ssh/id_ed25519 不匹配 bashPolicy,直接用宿主机权限读取成功。开启 sandbox.enabled=true 后,同样的 cat 会经过 sensitiveReadDeny 被拒绝。注意 sensitiveReadDeny 即使省略,Guard 也会合并源码中的默认敏感路径(含 ~/.ssh)。
三种路径/命令策略(入口不同)
| Agent 动作 | 主要经过 | 结果 |
|---|---|---|
write(".env", ...) |
protectedPaths.approval | 请求确认 |
edit(".git/config", ...) |
protectedPaths.block | 直接拒绝 |
Bash 执行 sudo apt update |
bashPolicy.directBlock | 直接拒绝 |
Bash 执行 rm -rf build |
bashPolicy.requireApproval | 请求确认 |
Bash 执行 cat ~/.ssh/id_ed25519 |
sensitiveReadDeny + OS sandbox | 操作系统拒绝 |
- protectedPaths:只管 Pi 的
write/edit工具,不自动拦截 Bash(printf 'secret' > .env不会被这层拦)。 - sensitiveReadDeny:拒绝 Pi 内置
read工具读取这些路径;sandbox.enabled开启时还会被转换成 OS 沙箱的读取限制,连 Bashcat一起拦。 - bashPolicy:值是 policy ID,不是正则;它不是完整的 Bash 解析器,也不是文件系统沙箱——Python、Node.js 或自定义工具绕过未识别命令时,仍需要 OS 沙箱提供底层边界。
network:当前只有全开/全关
"network": "open" // 允许所有出站网络,不是网站白名单
"network": "blocked" // 阻止所有出站网络,不是网站黑名单
当前不能在配置里写 {"allowedDomains": [...]}:pi-guard-sandbox 要求 network 是字符串,并转换成 allowedDomains: undefined(open)或 [](blocked)。底层 runtime 支持 allowedDomains,但这个 package 没把自定义名单暴露出来(源码里的 DEFAULT_NETWORK_ALLOWLIST 常量未被默认配置使用,不能当作生效白名单)。要实现域名白名单,要么用 Pi 官方 sandbox 示例,要么 fork 项目——最少修改 constants.mjs、config.mjs、sandbox-config.mjs(加 /guard network allowlist 还需 guard.mjs、extension.mjs),改后 npm test 至少确认 open → undefined、blocked → []、allowlist → 用户配置域名。
可选的 DCG
DCG(Destructive Command Guard) 是外部 Bash 风险判断工具,不是沙箱,也不是 pi-guard-sandbox 的 npm 依赖。确认启动 Pi 的环境能找到它(dcg --version;不在 PATH 用 export DCG_BIN=/path/to/dcg)。动作可选 allow、notify、confirm、block。DCG 只判断 Agent 调用的标准 bash 工具,不处理用户自己输入的 !cmd/!!cmd,也不扫描任意脚本文件的内部内容。真正的文件和网络边界始终来自 OS sandbox。
原文:AI萝卜《Pi沙箱实战:别再裸奔了》