跳到主内容
创建时间:最后更新:作者:白川

安全护栏类包:给 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 的扩展怎么注册工具和命令(第六章),以及项目信任机制(第十章第一节)。

三个包的全景

包版本许可证一句话核心机制
@aliou/pi-guardrails
0.22.0
MIT
给 Pi 加确定性安全检查
五个扩展分别管文件、路径、命令、审批上报、语义裁决
@gotgenes/pi-permission-system
5.18.1
—
集中式、确定性的权限闸门
启动前隐藏工具 + 每次调用的 allow/ask/deny
@aliou/pi-processes
0.13.5
MIT
长跑命令不阻塞对话
进程面板 + stdin 写入 + 事件唤醒

先记住这张表里最反直觉的一行:两个护栏包的"生效时机"不一样。 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 实际注册了五个:

扩展负责
guardrails
文件保护策略、设置、引导、示例
path-access
工作区外路径访问管控
permission-gate
危险 shell 命令确认或拦截
herdr
向 Herdr 上报审批提示
classifier
用语义判断代替硬规则(默认关闭)

安装和初始配置:

bash
pi install npm:@aliou/pi-guardrails

装完必须跑一次引导,否则保护策略没初始化:

text
/guardrails:onboarding    # 选初始配置
/guardrails:settings      # 交互式设置 UI
/guardrails:examples      # 看示例

文件保护

保护的是文件类型,不是路径。默认规则覆盖:.env、私钥、本地凭据、数据库 dump,以及你自己加的自定义路径。规则默认尊重当前工作目录。 也就是说 config/.env 在项目内被保护,但同级另一个目录里的 .env 不一定——这条行为和"工作区外路径访问"是两条独立的规则线,不要混着理解。

path-access:工作区外路径管控

这一层管的是"Agent 能不能跑到当前目录之外去"。读和写默认都检查,不是只拦写。越界访问有三种处理方式:

动作含义
allow
直接放行
block
直接拒绝
ask
弹提示问人

ask 模式下给你的授权粒度有三档:一次 / 本会话 / 始终。"始终"授权默认存到项目配置。 这一点直接连到后面的信任门槛:项目没被信任时,这份授权存不下来。

permission-gate:危险命令的识别与处置

它检测的是这几类:

类别例子
递归删除
rm -rf
特权命令
sudo、chown
磁盘格式化
mkfs、dd 写块设备
宽泛权限变更
chmod -R 777
自定义模式
你自己配的正则

命中后的处置选项有四个,最后一个是别的包都没有的:

选项行为
once
这次放行
session
本会话内放行
deny
拒绝
decline-and-stop
拒绝,并中止当前 turn

decline-and-stop 的意义是"人说了不,就别让模型换个姿势再试一遍"。配上 auto-deny 模式里的 terminate: true,效果是让 Agent 不再重试。它会穿透 $(...) 命令替换与进程替换。 这是这个包最有技术含量的一点:rm -rf $TARGET、cat <(echo secret) 这种间接构造也会被识别出来,而不是只做字面量匹配。

决策委托:让别的扩展来裁决

护栏包提供的是一个决策接口。别的扩展可以代它决定 allow 还是 block,规则是:

委托方给出的结果护栏的行为
全部 allow
免提示,直接放行
任意一个 block
阻止
其余情况(含超时)
回落到正常的人工提示

每一次决定都会在工具调用下方显示一行 [guardrails], 所以你能在对话记录里回溯"这次到底是谁放的行"。

内置 classifier:语义裁决,默认关闭

想按"这事该不该做"而不是"命令长什么样"来拦,可以开 features.classifier。它问三个问题:上下文是否充分?是否在用户请求范围内?影响有多大(0-4)?然后确定性地映射成裁决:

判定条件裁决
确不在范围内,且影响 >= 2
阻止
在范围内,且置信度低于破坏性阈值
允许
其余
升级为人工提示

这个映射是确定性的——同样的输入给同样的裁决,没有随机性。配置项:classifier.models 指定 provider 与模型,classifier.strategy 选 fallback / random / consensus。这三个值对应"主模型失败时怎么办"和"要不要多个模型投票"。

扩展事件与终端状态

护栏会发两个扩展事件,可以被你的其它扩展订阅:

text
guardrails:prompt:opened
guardrails:prompt:closed

opened / closed 这对事件名说明它包住了整个提示的生存期——你可以据此在侧栏显示"等待审批"的状态条。还有一个容易被忽略的细节:它会设置 OSC 7501 的 blocked 程序状态。Rex 这类支持 OSC 的终端不需要装任何扩展就能把 Pi 标成 blocked 状态。

配置与信任边界

配置有两条路:走 /guardrails:settings 的交互式 UI,或者直接改 JSON。

text
全局:~/.pi/agent/extensions/guardrails.json
项目:.pi/extensions/guardrails.json

这里有一个必须记住的规则:项目设置仅在 Pi 信任该文件夹后生效。 流程是 /trust 然后重启 Pi。未信任之前的行为很具体:

项目未信任时的表现
项目配置文件
被忽略,且不被修改
全局配置
照常生效
内置保护(.env、私钥等)
照常生效

所以"我配了项目规则但没反应"的排查方向永远是信任,而不是配置写错。

一个配套开关: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:先隐藏,再裁决

这个包的定位和护栏包不同:它是一个策略层,不是一个功能集合。 它不新增任何工具,唯一暴露的是一个命令:

text
/permission-system              # 无参数,打开设置面板
/permission-system show         # 查看当前生效策略
/permission-system path         # 查看配置路径
/permission-system reset        # 重置
/permission-system help

先说一件迁移的事:README 顶部标注这个包已迁至 gotgenes/pi-packages。 npm 包名不变,安装命令不变,但内容已经换了一代:

bash
pi install npm:@gotgenes/pi-permission-system

两段式拦截

它的核心机制是启动前 + 调用时两段:

阶段做什么为什么重要
启动前
把被禁的工具从工具列表里隐藏
模型看不见就不会浪费轮次去试探
每次调用
对可见的工具执行 allow / ask / deny 裁决
覆盖运行期才出现的输入(路径、参数)

"不浪费轮次试探"这一点是它和 guardrails 最实质的差异。 一个不知道 bash 被禁的模型,可能会先试一次、被拒、再想别的写法;被隐藏的模型直接就不想。

决策覆盖的操作类型

allow / ask / deny 三档决策作用在四类操作上:普通工具、bash、MCP、skill,以及一类被叫作 special operation 的操作。bash 规则支持通配符模式,这是它的强项:

json
{
  "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 文件,两层作用域

text
全局:~/.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。

bash
pi install npm:@aliou/pi-processes
# 或
pi install git:github.com/aliou/pi-processes

命令一整套:

text
/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 的选项里,值得先知道的几个:

设置作用
shell 路径覆盖
指定用哪个 shell
阻塞式后台模式转 process 工具
识别 &、nohup、disown、setsid 这类写法并转交给 process 工具
状态挂件开关
是否显示进程状态挂件
面板尺寸 / 日志浮层尺寸 / 历史上限 / 跟随模式 / 自动隐藏
日志面板的呈现细节
dock 默认状态与行数
底部 dock 的默认展开程度
工具暴露级别
direct / model-only / codemode / deferred / hidden

工具暴露级别只在 Global tab 里改,且下次启动才生效。 这一项直接决定模型能不能调用 process 工具——hidden 就是完全不给模型看,只给人看。

项目级配置同样要信任

text
项目级:.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。

两个护栏包的逐项对比

维度@aliou/pi-guardrails@gotgenes/pi-permission-system
生效层级
每次工具调用时
启动前隐藏工具 + 每次调用时
决策结果
allow / ask / deny / decline-and-stop
allow / ask / deny
bash 通配符
靠正则与模式匹配
tree-sitter 解析,支持 rm -rf *: deny
MCP 粒度
未声明按名控制
按 server / tool / skill 名控制
路径越界
path-access 扩展,读写都查,可 allow/block/ask
external_directory 提示 + 跨工具路径敏感规则
文件保护
独立扩展,管 .env / 私钥 / 凭据 / dump / 自定义路径
路径敏感规则覆盖 .env、~/.ssh/*
语义裁决
内置 classifier(默认关闭),确定性映射
无
决策委托
支持,其他扩展可代为 allow/block
无
事件
guardrails:prompt:opened / closed
无同类事件
终端状态
OSC 7501 blocked
无
模型调用
仅 classifier 需要
完全不需要
扩展数量
五个
策略层,不新增工具
配置形态
交互式 UI + 两层 JSON
单一 JSON,四层合并优先级
项目配置生效
/trust + 重启
/trust + 重启
是否沙箱
否,README 明写
否,README 明写

这张表里最该拿去做决策的是前三行。 如果你的痛点是"模型老想去试探被禁的操作",选 permission-system;如果是"模型会跑危险命令但我不介意它看见",选 guardrails;如果你的痛点是"路径判断复杂、需要跨工具统一规则",permission-system 的 tree-sitter 路线更可靠。

两边共有的一个硬约束:项目级配置都要 /trust + 重启 Pi。 这不是实现偷懒,是 Pi 的整体信任模型决定的(第十章第一节)。

都不是沙箱,那该选什么

@aliou/pi-guardrails 的 README 自己列了五个更接近的替代品:

包定位
@gotgenes/pi-permission-system
同类护栏,本篇已讲
@vtstech/pi-security
同类
@casualjim/pi-heimdall
同类
pi-sandbox
沙箱类
pi-container-sandbox
沙箱类

判断标准很简单:护栏管的是"要不要做",沙箱管的是"做了会碰到什么"。 如果你的场景里存在"Agent 需要真的跑一段不可信代码"(执行用户上传的脚本、处理外部仓库的构建),护栏帮不了你——它拦得住命令,拦不住命令执行后的副作用。

需要 OS 级隔离时,选沙箱类那一行。

顺带一提:hermes-memory 的 Secret Scanning

在上下文与记忆类包那一篇提过:hermes-memory 的 Secret Scanning 是生态里唯一做"API key / token / SSH key 禁止落盘"的实现。

这里是互补关系而非重复:护栏管的是"Agent 此刻能不能碰这个文件",Secret Scanning 管的是"这段内容会不会被记进长期存储"。两个都装是有意义的。

谁负责什么

职责由谁承担
判断某次调用该不该放行
护栏包(按你配的规则)
语义层面的"这事该不该做"
guardrails 的 classifier(默认关闭,需要模型)
判断命令里藏了什么
pi-permission-system(tree-sitter)或 guardrails(正则)
越界路径的最终裁决
你——ask 模式下每一档授权都是你点的
子 agent 的审批可见性
pi-permission-system(转发到主会话)
进程生命周期管理
@aliou/pi-processes
文件系统的实际隔离
沙箱,不在本文三个包的能力范围内
密钥是否落进长期记忆
hermes-memory 的 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 沙箱"这个区分讲给同事听——这是最值得花五分钟澄清的概念。

排障速查

现象检查点处理
项目里的护栏配置完全没生效
项目信任
/trust 后重启 Pi;未信任时项目文件被忽略且不被修改
所有路径都需要手动放行
配置语法
检查 JSON 尾逗号等语法错误——会让全部配置失效,表现为"什么都问"
pi-permission-system 的规则不生效
规则位置
确认写进了 config.json(或 agent frontmatter),不是别处
模型仍在试探被禁工具
生效层级
permission-system 要在启动前隐藏才有效,运行中改配置需重启
危险命令没被拦下
匹配方式
guardrails 靠正则,构造刁钻的命令可能漏;permission-system 用 tree-sitter 更可靠
子 agent 卡在审批上没人看见
转发
permission-system 会把 ask 转发到主会话,确认主会话 UI 在前台
classifier 不生效
开关
features.classifier 默认关闭,需显式开启并配 classifier.models
classifier 时好时坏
strategy
固定 classifier.strategy(fallback / random / consensus)
终端不显示 blocked 状态
终端能力
需要支持 OSC 7501 的终端(如 Rex)
"始终"授权存不下来
信任与作用域
未信任项目时默认存项目配置,改 pathAccess.alwaysScope: "global"
/ps:settings 改工具暴露级别没反应
生效时机
该项只在 Global tab 可改,且下次启动才生效
/ps 面板 Local tab 显示 "Project not trusted"
项目信任
/trust 后重启 Pi
长跑进程在 Windows 上不工作
平台
pi-processes 不支持 Windows,只有 macOS / Linux
/ps:kill 之后进程还在
行为细节
它会等进程真正退出或超时,不是发信号就返回
/ps:clear 没清掉东西
行为细节
它不碰运行中进程,只清已退出的记录
扩展报重复注册警告
依赖声明
宿主包必须在 peerDependencies;guardrails 只需 pi-coding-agent + pi-tui
想彻底隔离不可信代码
能力边界
护栏不是沙箱,换 pi-sandbox / pi-container-sandbox

参考资料