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 沙箱的读取限制,连 Bash cat 一起拦。
  • 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沙箱实战:别再裸奔了》