安全护栏类包:给 Agent 装上刹车
拆解三个安全与运行控制类 Pi 包——@aliou/pi-guardrails、@gotgenes/pi-permission-system、@aliou/pi-processes——按生效层级、决策粒度、bash 解析方式、MCP 控制粒度四个维度对比,并说明它们为什么都不是沙箱,以及项目级配置为什么都要先 /trust。
安全护栏类包:给 Agent 装上刹车
让 Agent 在你的机器上跑 rm -rf、cat .env、chmod -R 777 /,是所有"给 AI 装终端"的团队迟早要面对的问题。模型会误解指令,会被 prompt injection 诱导,会在一次看起来无害的 git clean 里顺手清掉你没打算删的东西。
生态里的应对分两种思路。一种叫护栏:在工具调用这条路径上装检查点,该拦的拦、该问的问。另一种叫沙箱:给 Agent 一个物理上碰不到你真实文件的环境。这两件事经常被混为一谈,但它们不能互相替代。
这批包最需要先纠正的一个印象是:护栏类包没有一个是沙箱。 @aliou/pi-guardrails 的 README 顶部直接挂着一块 WARNING,原文是:
I am not responsible for any issues, damage, or data loss caused by agents, even when using Guardrails. Guardrails is not a sandbox and does not guarantee safety.
@gotgenes/pi-permission-system 说得更简短:"This is a permission decision layer, not a sandbox"。意思是:护栏降低误操作的概率,不提供隔离保证。 它的价值在于"多一次确认、少一次手滑",而不是"就算模型疯了也碰不到你的文件"。
这篇讲三个包:@aliou/pi-guardrails(功能最全的护栏)、@gotgenes/pi-permission-system(以策略层为中心的权限闸门)、@aliou/pi-processes(长跑进程管理——严格说不算安全包,但它是"刹车"家族里最实用的一块)。读完你能回答三个问题:护栏在哪一层生效、两个护栏包怎么选、什么时候必须换沙箱。
前置知识:知道 Pi 的扩展怎么注册工具和命令(第六章),以及项目信任机制(第十章第一节)。
三个包的全景
先记住这张表里最反直觉的一行:两个护栏包的"生效时机"不一样。 pi-permission-system 会在 Agent 启动之前就把被禁的工具从工具列表里拿掉——模型根本看不到 bash,自然也不会用它去试探。pi-guardrails 则是每次调用时才检查,它的工始终在列表里,只是被拦下来。
这个差别在体验上非常明显:前者是"看不见的墙",后者是"撞上去才响的铃"。另一个差别是决策粒度:pi-permission-system 的 bash 规则支持通配符模式(rm -rf *: deny),因为它用 tree-sitter 真正解析 shell 语法;pi-guardrails 主要靠正则与模式匹配,覆盖面广但对刁钻的构造会漏。
@aliou/pi-guardrails:五个扩展拼起来的护栏
这个包的结构是一组独立扩展,不是一个单体。每个扩展管一件事,README 里列了四个,package.json 实际注册了五个:
安装和初始配置:
pi install npm:@aliou/pi-guardrails装完必须跑一次引导,否则保护策略没初始化:
/guardrails:onboarding # 选初始配置
/guardrails:settings # 交互式设置 UI
/guardrails:examples # 看示例文件保护
保护的是文件类型,不是路径。默认规则覆盖:.env、私钥、本地凭据、数据库 dump,以及你自己加的自定义路径。规则默认尊重当前工作目录。 也就是说 config/.env 在项目内被保护,但同级另一个目录里的 .env 不一定——这条行为和"工作区外路径访问"是两条独立的规则线,不要混着理解。
path-access:工作区外路径管控
这一层管的是"Agent 能不能跑到当前目录之外去"。读和写默认都检查,不是只拦写。越界访问有三种处理方式:
ask 模式下给你的授权粒度有三档:一次 / 本会话 / 始终。"始终"授权默认存到项目配置。 这一点直接连到后面的信任门槛:项目没被信任时,这份授权存不下来。
permission-gate:危险命令的识别与处置
它检测的是这几类:
命中后的处置选项有四个,最后一个是别的包都没有的:
decline-and-stop 的意义是"人说了不,就别让模型换个姿势再试一遍"。配上 auto-deny 模式里的 terminate: true,效果是让 Agent 不再重试。它会穿透 $(...) 命令替换与进程替换。 这是这个包最有技术含量的一点:rm -rf $TARGET、cat <(echo secret) 这种间接构造也会被识别出来,而不是只做字面量匹配。
决策委托:让别的扩展来裁决
护栏包提供的是一个决策接口。别的扩展可以代它决定 allow 还是 block,规则是:
每一次决定都会在工具调用下方显示一行 [guardrails], 所以你能在对话记录里回溯"这次到底是谁放的行"。
内置 classifier:语义裁决,默认关闭
想按"这事该不该做"而不是"命令长什么样"来拦,可以开 features.classifier。它问三个问题:上下文是否充分?是否在用户请求范围内?影响有多大(0-4)?然后确定性地映射成裁决:
这个映射是确定性的——同样的输入给同样的裁决,没有随机性。配置项:classifier.models 指定 provider 与模型,classifier.strategy 选 fallback / random / consensus。这三个值对应"主模型失败时怎么办"和"要不要多个模型投票"。
扩展事件与终端状态
护栏会发两个扩展事件,可以被你的其它扩展订阅:
guardrails:prompt:opened
guardrails:prompt:closedopened / closed 这对事件名说明它包住了整个提示的生存期——你可以据此在侧栏显示"等待审批"的状态条。还有一个容易被忽略的细节:它会设置 OSC 7501 的 blocked 程序状态。Rex 这类支持 OSC 的终端不需要装任何扩展就能把 Pi 标成 blocked 状态。
配置与信任边界
配置有两条路:走 /guardrails:settings 的交互式 UI,或者直接改 JSON。
全局:~/.pi/agent/extensions/guardrails.json
项目:.pi/extensions/guardrails.json这里有一个必须记住的规则:项目设置仅在 Pi 信任该文件夹后生效。 流程是 /trust 然后重启 Pi。未信任之前的行为很具体:
所以"我配了项目规则但没反应"的排查方向永远是信任,而不是配置写错。
一个配套开关:pathAccess.alwaysScope 设为 "global",就能在不信任项目的情况下保存"始终"授权。需要跨项目复用同一套越界白名单时,这是唯一路径。保存后的文件里带 $schema 字段,编辑器可以据此补全。
依赖方面很干净:peerDependencies 只要 pi-coding-agent >=1.5.0 和 pi-tui,不依赖 pi-ai 也不依赖 pi-agent-core——它不调模型。运行时依赖是 @aliou/pi-utils-settings 和 @aliou/sh。
@gotgenes/pi-permission-system:先隐藏,再裁决
这个包的定位和护栏包不同:它是一个策略层,不是一个功能集合。 它不新增任何工具,唯一暴露的是一个命令:
/permission-system # 无参数,打开设置面板
/permission-system show # 查看当前生效策略
/permission-system path # 查看配置路径
/permission-system reset # 重置
/permission-system help先说一件迁移的事:README 顶部标注这个包已迁至 gotgenes/pi-packages。 npm 包名不变,安装命令不变,但内容已经换了一代:
pi install npm:@gotgenes/pi-permission-system两段式拦截
它的核心机制是启动前 + 调用时两段:
"不浪费轮次试探"这一点是它和 guardrails 最实质的差异。 一个不知道 bash 被禁的模型,可能会先试一次、被拒、再想别的写法;被隐藏的模型直接就不想。
决策覆盖的操作类型
allow / ask / deny 三档决策作用在四类操作上:普通工具、bash、MCP、skill,以及一类被叫作 special operation 的操作。bash 规则支持通配符模式,这是它的强项:
{
"bash": {
"git *": "ask",
"rm -rf *": "deny"
}
}git * 命中所有 git 命令并要求确认,rm -rf * 直接拒绝。能这么写,是因为它用 tree-sitter-bash 和 web-tree-sitter 真正解析 shell 语法,而不是拿字符串正则去猜。
MCP 控制按 server / tool / skill 名三个粒度分档,不是一刀切。还有一组跨工具的路径敏感规则——.env、~/.ssh/* 这类路径在任何工具调用里都受同一套规则约束,不只是 read 受管。以及 external_directory 越界提示。
子 agent 的 ask 请求会被转发
一个容易被忽略但很关键的能力:子 agent 发出的 ask 请求会被转发到主会话的 UI。 否则你会遇到这种情况——主会话安安静静,子 agent 卡在等待审批的提示上,而那个提示根本没人看得见。
配置:单一 JSON 文件,两层作用域
全局:~/.pi/agent/extensions/pi-permission-system/config.json
项目:<cwd>/.pi/extensions/pi-permission-system/config.json全局路径尊重 PI_CODING_AGENT_DIR。合并优先级有四层,按顺序生效:全局文件 → 项目文件 → 全局 agent frontmatter → 项目 agent frontmatter。
运行时开关四个:debugLog、permissionReviewLog、yoloMode、piInfrastructureReadPaths。日志落在 ~/.pi/agent/extensions/pi-permission-system/logs/。yoloMode 是关掉全部 ask 的那个开关,名字来自这条工具生态的通用叫法。要用它之前先想清楚你在放弃什么。
一个会静默失效的坑
配置文件里的 JSON 语法错误(典型是尾逗号)会导致全部配置失效。 失效的表现不是"报错",而是**"什么都问"**——因为所有 allow 规则都没加载,只剩 deny 和 ask 的默认值。
这跟"配置生效了但规则写错了"症状几乎一样,但根因完全不同。排查顺序应该是:先确认 JSON 能被解析,再去调规则。
依赖:peerDependencies 是 pi-coding-agent * 和 pi-tui *;运行时依赖 tree-sitter-bash 和 web-tree-sitter。它不需要 API key,不调模型,纯宿主层策略。 这是它跟 guardrails 的 classifier 最大的分野。
@aliou/pi-processes:长跑进程这一块刹车
严格说这个包不属于安全类。但它解决的是同一族问题的一部分:Agent 失控地跑长命令。
它的场景很具体:让 Pi 启动 dev server、test watcher、本地 API 之后不阻塞对话,继续干别的活;等进程发出 ready / error / 退出信号时,自动唤醒 agent。
pi install npm:@aliou/pi-processes
# 或
pi install git:github.com/aliou/pi-processes命令一整套:
/ps # 主面板
/ps:logs [id] # 某个进程的日志
/ps:dock [expand|collapse|close]
/ps:pin [id] # 固定某个进程
/ps:kill [id] # 结束进程
/ps:clear # 清掉已退出的进程记录
/ps:settings唯一的工具是 process,其中 action: "write" 可以往运行中进程的 stdin 写字节,配 end: true 发 EOF。这是它能"给跑着的进程喂输入"的关键——比如给一个交互式 CLI 传确认。
结构上它是三个扩展加一个 skill:./extensions/processes/index.ts、processes-logs、processes-dock,以及 skills/pi-processes。
设置里的关键项
/ps:settings 的选项里,值得先知道的几个:
工具暴露级别只在 Global tab 里改,且下次启动才生效。 这一项直接决定模型能不能调用 process 工具——hidden 就是完全不给模型看,只给人看。
项目级配置同样要信任
项目级:.pi/extensions/processes.json需要 Pi 信任项目后才生效。 未信任时 Local tab 会显示 "Project not trusted",提示你 /trust 并重启 Pi。
平台限制
这个包不支持 Windows,只有 macOS 和 Linux。 另外要求 Node >= 22.19.0。两个行为细节值得记住:/ps:kill 会等待进程真正退出或超时,不是发个信号就返回;/ps:clear 不碰运行中的进程,只清记录。
依赖:peerDependencies 是 pi-ai *、pi-coding-agent >=1.4.0、pi-tui *、typebox *,不直接依赖 pi-agent-core。
两个护栏包的逐项对比
这张表里最该拿去做决策的是前三行。 如果你的痛点是"模型老想去试探被禁的操作",选 permission-system;如果是"模型会跑危险命令但我不介意它看见",选 guardrails;如果你的痛点是"路径判断复杂、需要跨工具统一规则",permission-system 的 tree-sitter 路线更可靠。
两边共有的一个硬约束:项目级配置都要 /trust + 重启 Pi。 这不是实现偷懒,是 Pi 的整体信任模型决定的(第十章第一节)。
都不是沙箱,那该选什么
@aliou/pi-guardrails 的 README 自己列了五个更接近的替代品:
判断标准很简单:护栏管的是"要不要做",沙箱管的是"做了会碰到什么"。 如果你的场景里存在"Agent 需要真的跑一段不可信代码"(执行用户上传的脚本、处理外部仓库的构建),护栏帮不了你——它拦得住命令,拦不住命令执行后的副作用。
需要 OS 级隔离时,选沙箱类那一行。
顺带一提:hermes-memory 的 Secret Scanning
在上下文与记忆类包那一篇提过:hermes-memory 的 Secret Scanning 是生态里唯一做"API key / token / SSH key 禁止落盘"的实现。
这里是互补关系而非重复:护栏管的是"Agent 此刻能不能碰这个文件",Secret Scanning 管的是"这段内容会不会被记进长期存储"。两个都装是有意义的。
谁负责什么
三种场景的可观察结果
场景一:模型试图读工作区外的密钥文件。 装 @aliou/pi-guardrails 并跑 /guardrails:onboarding。可观察结果:调用被 path-access 拦下,弹出 ask 提示,你选"本会话"后这一路径在本会话内不再问。如果你装的是 pi-permission-system,你会看到 external_directory 提示而不是路径授权三档——两者不提供同样的授权粒度。
场景二:模型执行 rm -rf 类命令。 装 @gotgenes/pi-permission-system,配 "rm -rf *": "deny"。可观察结果:命令被拒,且工具列表里根本看不到 bash——如果连 bash 都被禁了,那是启动前隐藏生效了,/permission-system show 能确认当前策略。
场景三:Agent 起了 dev server 之后卡住不动。 装 @aliou/pi-processes。可观察结果:命令不阻塞对话,进程出现在 /ps 面板里,输出 ready 之后 Agent 自动被唤醒继续干活。如果 /ps:settings 的 Local tab 显示 "Project not trusted",那说明项目没信任,/trust 后重启 Pi 即可。
你现在能做什么
- 装
@aliou/pi-guardrails,跑一遍/guardrails:onboarding选初始配置,再跑/guardrails:examples看它默认拦哪些。 - 装
@aliou/pi-guardrails,检查~/.pi/agent/extensions/guardrails.json是否存在$schema字段——有就说明写对了。 - 在没信任的项目里建一个
.pi/extensions/guardrails.json,重启 Pi,确认它被忽略且没有被修改;然后/trust+ 重启,确认它开始生效。 - 把
pathAccess.alwaysScope设为"global",验证在未信任项目里也能保存"始终"授权。 - 装
@gotgenes/pi-permission-system,跑/permission-system打开面板,看它的工具过滤设置;用/permission-system show确认生效策略,用/permission-system path确认配置落在哪个文件。 - 在 permission-system 的配置里故意加一个尾逗号,重启后验证症状是"什么都问"——记住这个症状,下次好认。
- 装
@aliou/pi-processes,起一个长跑命令,观察/ps面板和自动唤醒;再用process工具的action: "write"+end: true给它发个 EOF。 - 在 permission-system 里打开
debugLog,确认日志落在~/.pi/agent/extensions/pi-permission-system/logs/。 - 对照两个包的 README,把"护栏 vs 沙箱"这个区分讲给同事听——这是最值得花五分钟澄清的概念。