@heathhe/pi-guard
Session-scoped guard modes and OS-sandboxed Bash for Pi
Package details
Install @heathhe/pi-guard from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@heathhe/pi-guard- Package
@heathhe/pi-guard- Version
0.1.0- Published
- Aug 5, 2026
- Downloads
- 124/mo · 7/wk
- Author
- heath-jian
- License
- MIT
- Types
- extension
- Size
- 291.3 KB
- Dependencies
- 3 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./extensions/pi-guard.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@heathhe/pi-guard
Pi 的 session-scoped 执行护栏:safe、full-auto、yolo 三种运行模式,加上由 @anthropic-ai/sandbox-runtime 提供的 Bash OS sandbox。运行时不依赖 @gotgenes/pi-permission-system。
安装与启动
从 npm 安装:
pi install npm:@heathhe/pi-guard
本地开发:
npm install
pi -e ./extensions/pi-guard.ts --safe
# 或使用 wrapper(安装包后)
pi-guard --full-auto [...pi args]
CLI flag --safe、--full-auto、--yolo 互斥。extension 也注册同名 Pi flags,因此显式加载 extension 时可使用 pi --yolo。初始 mode 按 Pi flag → PI_GUARD_MODE → 全局 defaultMode 解析。
从其他权限扩展迁移时,请在同一 Pi 进程中禁用旧扩展。多个权限扩展会各自收到事件,旧扩展仍可能弹窗或阻断;pi-guard 不会修改或接管它们的配置。
配置
唯一持久配置是 ~/.pi/agent/pi-guard.json。它是创建新权限会话时使用的默认模板,不是进程内所有 live session 共用的动态 policy。/guard settings 验证并原子写入默认模板,同时只把候选配置应用到发起命令的当前 session;其他已运行 session 保持各自的 effective config、mode、grants、identity、policy generation 和 sandbox worker,之后启动的 session 使用新默认。运行时 mode 切换不会改写 defaultMode,也不会写 Pi session entry。
{
"defaultMode": "full-auto",
"networkMode": "proxy",
"allowDomains": ["github.com", "*.github.com"],
"denyDomains": ["tracking.example.com"],
"allowLocalBinding": false,
"allowCommands": ["git", "echo"],
"denyCommands": ["curl"],
"commandRules": [
{ "pattern": ["git", { "anyOf": ["status", "diff", "log"] }], "decision": "allow" },
{ "pattern": ["git", "push"], "decision": "confirm", "justification": "Review the destination before pushing" },
{ "pattern": ["npm", "publish"], "decision": "deny", "justification": "Publish through CI" }
],
"allowTools": ["memory_search"],
"confirmTools": ["mcp_publish"],
"denyTools": ["dangerous_tool"],
"unknownToolPolicy": "confirm",
"sensitiveDirectories": ["~/.ssh", "~/.aws", "~/.gnupg"],
"readOnlyDirectories": ["~/shared-policy"],
"protectWorkspaceMetadata": true,
"writableDirectories": ["~/scratch"]
}
字段语义:
defaultMode:safe、full-auto或yolo;默认full-auto。networkMode:off、direct或proxy;默认off。它只约束 pi-guard 包裹的bash/user_bash子进程,不是整个 Pi、MCP、浏览器或其他 extension 的全局网络开关。off:sandboxed Bash 外连网络全禁;文件系统 sandbox 继续生效。direct:允许进程直接建立原始 TCP/UDP 连接,不经过域名过滤代理;文件系统 sandbox 仍继续生效。此模式不会应用allowDomains或denyDomains,应视为明确的网络扩权。proxy:由 sandbox-runtime 网络代理按 allowlist-first 策略限制域名。
allowDomains:仅供proxy使用的 sandbox-runtime 域名 allowlist;默认[]。空 allowlist 在proxy下仍阻断所有外连;direct下该字段被忽略。denyDomains:仅供proxy使用的域名 denylist;默认[]。deny 先于并覆盖 allow,用于从较宽的 allow pattern 中 carve out 明确禁止的目标;direct下该字段被忽略。allowLocalBinding:在off/proxy下是否允许进程监听本机端口;默认false。direct已是不受网络限制的模式,因此该开关不形成额外限制。allowCommands:仅在safe下减少普通 Bash 确认的命令 basename 表;默认[]。只有 Tree-sitter 能证明由静态 plain words 与&&、||、;、|组成的线性命令才会应用自动放行;变量展开、assignment、glob、重定向、substitution、control flow、subshell 和后台执行仍确认。denyCommands:在safe和full-auto下都拒绝的命令表;默认[]。commandRules:参数 token prefix 规则;默认[]。每条规则包含非空pattern、allow/confirm/denydecision,以及可选justification。pattern token 可以是精确字符串或{ "anyOf": [...] };多条规则或 compound command 同时命中时取最严格结果(deny > confirm > allow)。静态线性 shell 按每个 effective command 分别匹配;复杂 shell 只作为["bash", "-c", "<完整脚本>"]匹配,因此不会被宽泛的gitallow 规则误放行。allowCommands/denyCommands保留为 basename 兼容层。allowTools/confirmTools/denyTools:工具名精确、大小写敏感的决策表;默认均为[]。三张表不允许出现同一个工具名。优先级为 deny → path/sensitive boundary → confirm → allow,因此allowTools不能绕过敏感目录、read-only carve-out 或写根边界。unknownToolPolicy:没有出现在上述工具表、也不是内置bash/read/grep/find/ls/write/edit的工具如何处理;allow、confirm或deny,默认confirm。确认按工具名与完整 canonical JSON input 的 SHA-256 精确复用;提示只显示经过 best-effort secret-key 脱敏和长度限制的 input。sensitiveDirectories:完整的敏感读写保护目录表。guarded mode 下这些目录既加入 sandboxdenyRead/denyWrite,也由 tool policy 硬拒绝;once/session 写根审批不能覆盖。省略字段时默认为~/.ssh、~/.aws、~/.gnupg;显式[]会保持为空,不会恢复默认值。settings UI 保存空表前会单独警告并再次确认。readOnlyDirectories:额外的“允许读、默认禁止写”路径;默认[]。命中后进入 tool-scoped once/session/deny 写审批,不像sensitiveDirectories那样硬拒绝。protectWorkspaceMetadata:默认true,把每个启动 workspace 下的.git、.agents、.codex作为隐式 read-only 路径。写类 Git 子命令因此会先审批.git/**;关闭前 settings UI 会单独警告。writableDirectories:额外可写根目录;默认[]。启动 workspace 与 OS 临时目录始终是隐式可写根。
旧配置兼容规则:若文件省略 networkMode 但包含非空 allowDomains,加载时自动解释为 proxy,保持旧版域名 allowlist 行为;省略 networkMode 且 allowDomains 为空或不存在时解释为 off。下一次经 /guard settings 保存后会写入显式的新字段。
调用级网络授权
guarded Bash 的普通执行授权与网络能力是两个独立维度;允许一条 Bash 命令不等于允许它联网。pi-guard 对静态 literal 网络目标执行保守分类:HTTP/HTTPS 进入域名代理授权,localhost / *.localhost / loopback HTTP 进入独立 local-service 授权,MySQL/PostgreSQL/Redis/SSH/nc 等原始连接进入 raw-direct 授权。变量、substitution、Node/Python 脚本或配置文件决定的目标不会被静态解析结果当作安全边界,仍先在 baseline sandbox 中 fail closed。
获批网络调用始终在一次性的 ephemeral sandbox worker 中执行;baseline session worker 永远不通过 updateConfig() 临时扩权。临时 worker 携带当前完整 filesystem policy和本次已批准 write roots,执行结束、取消、超时或异常后尽力停止被管理的进程组。HTTP session grant 按 (requester session, tool, canonical host set, policy generation) 复用,因此同域名 URL 的 path/query 变化不重复提示;每次执行仍创建新 worker。deny domain、敏感路径、Git config/hooks和 policy generation 失效优先于 session grant。
raw-direct 的当前真实 enforcement 是“本次 exact command 的受管前台进程组临时获得不限目标的 outbound network(最长 300 秒)”,不是只允许 UI 中检测到的 host:port。UI 必须显示这个事实;raw session scope 只跳过相同 exact command 的后续提示,仍逐次使用 ephemeral worker。worker 结束时执行 best-effort process-group cleanup,但不能保证回收通过 setsid / double-fork 脱离该进程组的 daemon,所以 raw-direct 拒绝已识别的 compound、background 和 daemon-style 命令。若需要不可绕过的完整 descendant 回收,还需要 cgroup v2、macOS 专用 supervisor 或 Windows Job Object;当前实现不作这项保证。若需要对任意 MySQL/PostgreSQL 客户端强制做到单个 host:port,现有 sandbox-runtime 无法在不增加透明网络 broker/OS 重定向的情况下表达,pi-guard 不会把 direct 冒充成精确目标授权。
pnpm/npm/yarn/bun install/add/view、npx/pnpx、corepack prepare 等明确的动态包管理联网操作会在首次执行前直接请求 exact-command raw-direct 授权,因为目标可能来自 registry 配置与 lockfile,sandbox proxy 又可能只返回通用 403,--silent 甚至可能没有 stderr;显式 --offline 不触发网络授权。其他非 direct profile 中失败且 stderr 命中 EPERM、ENOTFOUND、Operation not permitted、resolve/network-unreachable 等特征时,pi-guard 会追加结构化 policy diagnostic:该现象与 sandbox 网络拒绝一致,不能据此断定 VPN、DNS、数据库或远端服务故障。动态包管理失败诊断仍作为低层防御,并明确失败也可能来自包元数据、认证或 registry 配置。失败命令不会自动重放,避免重复副作用;仅当用户或 Agent 显式重试同一 tool 的同一 exact command 时,才进入带完整 unrestricted 提示的 raw-direct 审批。失败指纹不适用于不同命令,policy generation 变化时清除。
配置使用严格 schema:未知顶层字段会拒绝加载/保存,避免安全键拼写错误被静默忽略。所有 list 必须是 non-empty string 数组,条目会 trim、去重并拒绝 NUL。目录支持 ~/,并在发布策略前通过 realpath/最近存在祖先方式 canonicalize;workspace、OS 临时目录和额外可写目录形成初始 canonical write roots,read-only 路径作为 denyWrite carve-out。这份 policy 同时用于 sandbox、Bash mutation/redirect 检查、built-in file tools 和带 path 字段的未知工具。
canonical writableDirectories 若包含 filesystem root、home 目录或 home 的祖先,会被诊断为 dangerously broad:root session 启动和 /guard status 显示 warning,settings UI 保存前要求额外确认。手工 JSON 配置不会被静默改写或拒绝;hard deny 与敏感目录保护仍生效,但这些 root 会显著扩大 sandbox 写边界和 full-auto 自动放行范围。
safe 和 full-auto 遇到初始 roots 外的可识别写入时不会修改全局 roots:root UI 逐项显示 requester、工具名、当前操作 summary、检测到的 canonical paths,以及真实授权边界的 canonical root patterns(例如 /outside/**)。选择恰好有三种:单次授权(仅当前调用)、整个会话授权或拒绝;取消按拒绝处理。多 root 请求会逐项展示全部 pattern,不把目录授权描述成 file-exact。
整个会话授权只保存在按 ctx.sessionManager.getSessionId() 路由的 SessionState 内存中,cache key 是 (toolName, canonical root)。后续请求必须是同一工具,并且本次全部 roots 都被该工具的 session grants 覆盖,才会免提示;例如 write 的 /outside/** grant 不会授权 edit、bash 或 user_bash。单次授权不写 cache,下一次相同请求仍会询问。两种批准都不会写入 pi-guard.json、不会加入 baseline allowedWriteRoots,也不会重启其他 session 的 sandbox worker。敏感路径、hard deny 与 denyCommands 在审批前决定,任何授权 scope 都不能覆盖。
safe 的普通确认同样提供且只提供 单次授权、整个会话授权和拒绝。普通 session grants 使用与 write-root grants 分离的内存 cache:Bash 按工具名与完整原命令的 SHA-256 精确匹配,bash 和 user_bash 互不复用;workspace 外 read 按 canonical exact path;root 内 write / edit 按工具名与 canonical exact path,正文变化不会扩大或缩小该路径授权。提示显示 requester、工具、受限长度的操作摘要和授权 target;write/edit 只显示行数、字符数等统计,Bash 会对常见 secret-like 参数做尽力脱敏。root 本地取消、提示异常或无 UI 均 fail closed;child 或更深 descendant 即使自身有 UI 也不会本地提示,而是使用下文的签名转发直接请求 root,root 不可达或协议失败同样拒绝。
模式与热切换
- safe:普通
bash/write/edit以及 workspace 外read使用上述 exact-target once/session/deny 授权;root 本地无 UI 时阻断,child/descendant 则必须转发给可用的交互 root。若 Bash AST 中每一个 effective command 都在allowCommands中,则只跳过这次普通授权。hard deny、用户 deny、敏感读写和 canonical 写边界仍执行;“越出 baseline write roots 的非敏感写入”使用独立的 write-root once/session/deny 审批,Bash 仍在 OS sandbox 中运行。 - full-auto:root 内普通操作免确认;
denyCommands、hard deny、敏感读写和 canonical 写边界仍执行;可审批的越界写仍要求 root 用户确认,Bash 在 OS sandbox 中运行。allowCommands在此模式无效。 - yolo:关闭审批、pi-guard policy、hard deny 和 sandbox,使用本地 Bash 与 unrestricted tools。状态栏会持续显示醒目警告。
统一决策优先级
以下结果按表格从上到下匹配,先命中者生效:
- allow:不显示 pi-guard 用户提示;guarded Bash 仍受 OS filesystem sandbox 和所选
networkMode约束。 - confirm:由用户选择 exact target 的 once / session / deny;session 仅按同工具与 exact grant key 复用,取消、无交互 UI 或提示失败均拒绝。
- write-root-approve:由 root 对 canonical write root 选择 once / session / deny;只有同工具的 session scope 可在当前 session 复用,拒绝或协议失败均不扩权。
- network-approve:独立于 Bash/write-root 决策;HTTP/local-service 与 raw-direct 使用不同 capability,Root/Child grant 不互相继承,baseline worker 永不扩权。
- deny:直接拒绝且 fail closed;后续 allowlist、普通确认或 session 审批都不能覆盖。
| 条件或入口 | safe |
full-auto |
yolo |
|---|---|---|---|
| parser、policy、sandbox 或 lifecycle 无法证明安全 | deny | deny | policy/sandbox 不运行 |
built-in hard deny(如 sudo、recursive forced rm) |
deny | deny | allow |
命中 denyCommands |
deny | deny | allow |
命中 deny commandRules |
deny | deny | allow |
命中 sensitiveDirectories 的读写 |
deny | deny | allow |
| 可识别的非敏感写入越出当前 write roots | write-root-approve(once/session/deny) | write-root-approve(once/session/deny) | allow |
Bash 未触发敏感/越界写检查,且命中 confirm commandRules |
confirm | confirm | allow |
Bash 未触发敏感/越界写检查,且每个 effective command 都在 allowCommands |
allow | allow(该字段不参与判断) | allow |
| 其他未触发敏感/越界写检查的 Bash | confirm | allow | allow |
非敏感 read 越出 workspace |
confirm | allow | allow |
非敏感 read 位于 workspace 内 |
allow | allow | allow |
grep / find / ls 读取 sensitive path |
deny | deny | allow |
写入 workspace metadata 或 readOnlyDirectories |
write-root-approve(once/session/deny) | write-root-approve(once/session/deny) | allow |
非敏感 write / edit 位于 write roots 内 |
confirm | allow | allow |
命中 denyTools |
deny | deny | allow |
命中 confirmTools |
confirm | confirm | allow |
命中 allowTools 且没有触发更高优先级边界 |
allow | allow | allow |
| 其他未知工具且没有触发 path boundary | 按 unknownToolPolicy |
按 unknownToolPolicy |
allow |
这里的“不可覆盖”指 guarded mode 内的优先级:allowCommands、普通确认和 write-root once/session approval 永远不能推翻 hard deny、denyCommands、敏感路径拒绝或 fail-closed 错误。显式切换到 yolo 会关闭整个 pi-guard 边界,而不是覆盖某一条拒绝规则。
根 session 可使用:
/guard status
/guard explain
/guard doctor
/guard settings
/guard safe
/guard full-auto
/guard yolo
/guard status 分区按工具列出当前 session 已批准的 ordinary exact targets 与 write root patterns;不会显示内部命令 hash,过长 target 会截断,条目过多时会显示省略数量。两类授权只存在于内存,session 结束后清空;mode/config generation 更新不会把它们并入 baseline,也不会把它们持久化,并会清空 exact/write-root grants、pending Bash grants 和并发 approval 状态,避免旧授权覆盖新规则。若配置包含 dangerously broad writableDirectories,status 同时列出 warning 并使用 warning 级别通知。status 还显示签名 policy generation、运行时 child launcher enforcement 和 custom agent 的声明式 extension coverage;/guard doctor 在 launcher 生效时把未显式声明 guard 的定义标为冗余诊断,而不是要求修改 agent 文件。
Ctrl+Shift+Y 快速进入/退出 YOLO;退出时回到内存中记住的最后一个 guarded mode(没有可用记录时回退到配置的 guarded default,再回退到 full-auto)。每次从 guarded mode 进入 YOLO 都必须经过交互确认;无 UI 或取消确认都是 no-op。
所有 mode 和配置生命周期操作使用当前 session 自己的串行队列;A 的切换、保存或 shutdown 不会排队、reset 或覆盖 B:
safe↔full-auto只更新该 session 的运行态,不写进程级 mode environment,也不重启 sandbox。- guarded → YOLO 先发布
not ready,等待该 session 的 sandbox worker 停止后才发布 YOLO mode。 - YOLO → guarded 先发布目标 guarded mode 与
not ready,再初始化该 session 的 parser/sandbox worker;全部成功后才变为 ready。 - guarded 状态下 baseline filesystem/network policy 变化只替换当前 session 的 worker,并清空当前 session grants;交互式 once/session grants 自身不重启 worker。command/tool-only 变化不重启 sandbox。
- 配置先验证并完成所需 runtime 准备,再使用同目录临时文件 + rename 原子持久化,最后发布到内存。reinitialize、network update 或持久化导致 runtime 一致性无法证明时保持 fail-closed,不降级到本地 Bash。
/guard settings 仅在交互 UI 中可用。它可选择 runtime/network/unknown-tool policy,切换本地监听与 workspace metadata 保护,并逐行编辑所有 list 字段。每行会 trim,空分隔行会丢弃;取消 select/editor/确认不会修改磁盘、runtime 或 lifecycle。/guard status 会显示当前网络与工具 policy,以及 read-only 保护数量。
Command policy 顺序与匹配
Bash 必须先由 Tree-sitter Bash grammar 成功解析。每条 guarded Bash 的顺序固定为:
- parser 与 built-in AST hard deny;
denyCommands,以及取最严格结果后的 denycommandRules;复杂 shell 仅匹配显式bash -cwrapper rule;- 明显 mutation/redirect path checks、workspace metadata/read-only carve-out,以及写类 Git 子命令的本地或外部 metadata 检查;若唯一问题是需要额外写权限,则进入 once/session/deny 写根审批;
- 应用剩余的 confirm / allow
commandRules; - 仅当 shell 结构可安全拆分为静态命令时,在
safe下检查尚未由 prefix rule 决定的 effective commands 是否属于allowCommands; - safe 普通确认或 full-auto 普通放行。
Command entry 按 AST 中 effective command 的basename/参数 token 精确、大小写敏感匹配;/usr/bin/git 的 effective basename 是 git。解析会穿透支持的常见 wrapper(例如 command、exec、env、nohup、xargs)。静态多命令或 pipeline 中只要有一个 command 未获 allow,safe 仍需确认;任一命中 denylist 或 deny prefix rule 则拒绝。
allowCommands 只是 convenience filter,不是完整 Bash 安全边界。它永远不能绕过 sudo/doas、shutdown/reboot、mount/umount、recursive forced rm 等 built-in hard deny,不能绕过路径和敏感目录检查,也不能关闭 sandbox。AST hard deny 与 command extraction 都不是完备的行为分析;实际 Bash filesystem/network 行为边界来自 OS sandbox。
!command / !!command (user_bash) 复用相同 parser、policy、safe 授权和 sandbox operations,但 ordinary session grant 与 built-in bash 按工具名隔离。parser、policy、sandbox 初始化或执行失败均 fail closed。
Tool gate
tool_call 将 read/grep/find/ls 作为只读工具处理:guarded mode 都拒绝 sensitive reads,safe 对 workspace 外读取做 exact approval。write/edit 与 path-bearing custom tools先应用 sensitive、read-only 和 write-root 边界。边界通过后,custom tools 再按 denyTools、confirmTools、allowTools 与 unknownToolPolicy 决策。Pi 的 tool definition 没有可信的 side-effect annotation,所以未知工具默认 confirm;exact prompt 对常见 secret-like key 做 best-effort 脱敏。hard deny、denyCommands、denyTools、敏感读写和 lifecycle 错误不可被任何审批覆盖。
Root 与 child
session_start 以 ctx.sessionManager.getSessionId() 建立独立 SessionState,并为每次 live root activation 生成随机 guard session ID 与 Ed25519 identity。root 的 mode、guard ID、public key、snapshot path/digest 和 grants 都不写入共享 process.env。snapshot 包含 mode、完整 GuardConfig、root workspace、guard extension digest、单调 generation 和 policy digest;每次发布创建不可变的 generation-<n>-<digest>.json,避免 wrapper 与 child 分别读取可替换 current.json 的竞态。mode/config generation 更新会清空当前 session grants,并使旧 generation child 在下一次受保护操作时 fail closed。
PI_SUBAGENT_CHILD=1 时 extension 要求 snapshot path、root session ID、approval public key 和 guard digest 完整匹配,并验证 snapshot schema、content digest 与签名。child 使用 snapshot 的 mode/config,不再读取自己的 ~/.pi/agent/pi-guard.json,也忽略 child flags/default。child 不能热切换 mode,也不能用 settings 修改 policy;必须从 root 操作。snapshot 缺失、篡改、session/digest 不匹配、parser/sandbox 初始化失败或 readiness channel 失败时,required child 调用 shutdown() 并保持所有工具 fail closed。
所有 descendant 都使用统一的 v5 签名审批协议直接向 root 转发 write-root、safe 普通 exact-target 与独立 network 请求,不由中间 child 代批。每一层把有界 PI_GUARD_APPROVAL_DEPTH 递增,超过 32 层拒绝初始化。request 是严格 discriminated union;全部公共字段与类型字段都进入 request digest,root 的 Ed25519 signed response同时绑定 request hash 和批准 scope(once 或 session)。未知字段、跨 kind 字段、旧版本、缺字段、scope 篡改、伪造、响应重放、marker mismatch、root 不可达、超时或协议/IO 错误都 fail closed。协议 session 目录要求当前用户拥有的真实 0700 目录,请求、响应和 ready marker 使用原子 0600 文件;预创建或符号链接异常拒绝启动/转发。
child 在 exact 转发前执行完整本地 policy 与 canonical/sensitive validation,验签批准后再次验证,只有发起 child 会把 session scope 写入自身 (toolName, grantKey) 内存 cache;root 不记录 child exact grant,once 不缓存。相同 child/tool/grantKey 的并发请求合并成一次 root prompt,但各调用仍独立执行;不同 target 不批量授权。同一 child 达到第 10 个不同 forwarded exact prompt 时,root 只警告一次,绝不会自动切换 mode 或扩大 scope。
Bash 的完整原命令仅在 child 本地用于 SHA-256 grant key;转发与 root UI 只携带 policy 生成的脱敏、截断 summary/authorization targets,内部 hash 不显示在 prompt、status 或普通日志。regex 脱敏是 best-effort:需要精确人工审查的高风险 Bash 应保持命令简短,稳定的常用安全命令优先加入 allowCommands。forwarded write-root session grant 同样只记录在发起 child,并按工具/root 隔离。每次 child 受保护操作都会验证 Root route、guard identity 和 policy digest仍存活;Root policy generation 更新、route replacement 或 shutdown 会撤销旧 child 的本地 session grants 使用路径并 fail closed。本阶段不提供 spawn-time preauthorization。
Git write root 只在启动时检测一次且不执行 shell:默认 metadata protection 把 workspace .git 作为 read-only root;.git 指针文件还会解析 gitdir/commondir,识别 workspace 外的 common metadata root;workspace 位于祖先仓库子目录时审批 canonical 祖先仓库根。commit、merge、rebase、reset、checkout、clean 等写类 Git 子命令会在执行前审批相应 root;status、diff、log 等普通只读子命令不会因此提示。漏检的实际写入仍由 OS sandbox 的 read-only carve-out 或 write allowlist 拒绝。
.git/config 与 .git/hooks/** 是更窄的强制 deny carve-out:pi-guard 对 workspace、linked-worktree common metadata 和祖先 repository root 都生成显式 canonical deny 路径。即使用户批准整个 Git metadata root、把它放进 writableDirectories,或关闭 protectWorkspaceMetadata,这些路径仍不开放;因此需要改写 config 的 git remote 等操作可能仍会被 sandbox 拒绝。该限制是刻意保留的,目前没有 allowGitConfig 扩权开关。
进程只共享一个无状态 PI_SUBAGENT_PI_BINARY wrapper 和 owner-only launch registry directory。每个 Root 在 registry 中拥有独立 route;direct child 由每次 spawn 的 PI_SUBAGENT_PARENT_SESSION 精确选择 route,wrapper 再向 child 注入 sticky PI_GUARD_ROOT_ROUTE_ID,因此 nested child 即使 immediate parent session ID 改变也保持原 Root。pi-subagents 的标准 foreground/background/parallel/chain/fanout/retry/resume/nested spawn 使用该 override;unknown、stale 或已 shutdown 的 route 一律 fail closed。wrapper 保留原 argv,并在 task 前强制追加显式 --extension <pi-guard.ts>,所以 agent 的显式/空 extensions:、defaultExtensions: [] 或 denyExtensions=true 不能从标准 child launch 中移除 guard。已有同一路径 wrapper 会加入共享 registry,不再误判为递归;原有不同 downstream launcher 仍被链式调用。
wrapper 为每个 child 创建 owner-only readiness channel。child 只有在 snapshot 验签、paths/parser/sandbox、hooks 与 approval client 初始化完成后才写入绑定 nonce/session/policy/extension digest 的 attestation;缺少或不匹配时 wrapper 终止 child 并返回非零。root 的 /guard doctor 仍只读扫描 user/project custom agent definitions,但 launcher 生效时 0 covered / N explicitly uncovered 只表示这些定义缺少声明式冗余,不再要求逐个编辑 subagentOnlyExtensions。pi-guard 不会修改任何 agent definition。
该强制加载保证仅覆盖遵守 PI_SUBAGENT_PI_BINARY 的标准 pi-subagents 执行链。其他 extension 自行 spawn 真实 Pi、直接 Node API 或用户绕过受控 root 启动入口,不在这一行为保证内。
安全边界
- OS sandbox 当前只覆盖 built-in
bashoverride 与user_bash。每个 guarded live session 启动独立的pi-guard-sandbox-worker进程;每个 worker 独占一份 staticSandboxManager、network proxy、bridge、credential registry 和 lifecycle。Desktop 主进程不再调用共享SandboxManager.initialize/updateConfig/reset,因此 A 的 network 更新、YOLO 或 shutdown 不会改变 B。 - 额外 Bash 写批准通过该 session worker 的 per-execution filesystem
customConfig生效:加入该调用的 extra roots,始终保留 sensitive deny,并且只在批准 root 与某个 read-only carve-out 完全相同时临时移除该 carve-out;批准其祖先不会顺带取消嵌套保护。macOS/Linux 已覆盖并发 worker、交叉 workspace 写拒绝和独立 teardown 集成测试。Windows 并发 helper 尚未完成 ACL/WFP 交叉验证,因此检测到第二个并发 guarded session 时 fail closed。Linux 仍需 sandbox-runtime 的平台依赖(如 bubblewrap)。 write/edit依赖最终tool_callgate,不是完整 filesystem broker。- 其他 extension、MCP、直接 Node API 或未经过该 gate 的执行路径可以绕过这些控制。
- child wrapper、签名 snapshot 和 readiness attestation 提供标准
pi-subagents路径的自动继承与 fail-closed 启动,不是对同进程恶意 extension 的不可绕过信任根。 - 未知工具只对谨慎识别出的 path 字段做边界检查。
- YOLO 明确绕过全部 pi-guard 控制;确认只降低误触风险,不改变其能力。
- 这不是最终的 Codex 安全等价实现;sandbox-runtime 本身仍是 research preview,Linux 还需其平台依赖(如 bubblewrap)。
开发验证
git diff --check
npm run check
npm test
npm run pack:check