@fanchaozz/pi-sentinel
A pi extension that protects sensitive information (PII & credentials) in coding-agent sessions: inbound tokenization, outbound plaintext restoration for tool calls, derived masked display, and zero-plaintext session persistence.
Package details
Install @fanchaozz/pi-sentinel from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@fanchaozz/pi-sentinel- Package
@fanchaozz/pi-sentinel- Version
1.0.1- Published
- Sep 22, 2026
- Downloads
- 391/mo · 231/wk
- Author
- fanchaozz
- License
- MIT
- Types
- extension
- Size
- 240.8 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
🛡 Pi Sentinel
pi coding agent 的敏感信息防护扩展 —— 入向 token 化 / 出向明文恢复 / 落盘零明文。
为什么需要
AI 编码代理在工作中不可避免接触敏感信息:日志里的手机号、配置文件里的密钥、数据库查询结果里的身份证号。这些值一旦以明文进入会话,就会:
- 发送给 LLM 供应商——脱离你的机器控制
- 持久化到会话历史(session.jsonl)——随分享/归档扩散
Pi Sentinel 在 pi 的钩子层拦截这三条通道,让敏感值对 LLM 不可见、对工具可用、对历史零残留。
核心能力
| 能力 | 说明 | 示例 |
|---|---|---|
| 🔒 入向 token 化 | 用户输入/工具输出中的敏感值替换为可逆占位符 | 13900000001 → 13900000001 |
| 🔓 出向还原 | 工具调用参数中的 token 自动还原为明文——LLM 能指挥工具,但看不到原值 | SELECT * WHERE phone='13900000001' → 工具收到真实号码 |
| 🎭 派生展示 | token + 脱敏事实,LLM 可见脱敏形态 | 13900000001|138****0000 |
| 🧹 落盘兜底 | 会话历史写入前再过滤一遍,assistant 复述的明文也拦 | session.jsonl 零完整明文 |
| 📋 45 条内置规则 | 16 类敏感类型:PII(手机/身份证/银行卡…)+ 凭据(API key/JWT/私钥…)+ 29 个厂商 key 子项 | AWS/GitHub/Stripe/OpenAI/Anthropic… |
| ✏️ 自定义规则 | 正则/关键词两种匹配 + 规则级动作覆盖 | 匹配内部 VIP 会员号格式 |
| 🧪 派生算子 | 4 种类型无关算子 + “期望输出”自动反推参数 | 填原文+138****0000 → 自动得 mask(3:4) |
| 🖥 TUI 管理面板 | 策略/查询/状态三面板,全键盘操作 | /sentinel:policy |
| 🔐 加密存储 | 原值 AES-256-GCM 加密存本机 | ~/.pi/sentinel/store/ |
工作流程
不装插件时:明文直接穿透
你粘贴日志(含手机号 / API key 等敏感值)
│
▼
明文进入对话上下文 ──→ 随每次请求发送给 LLM 供应商
│
▼
明文写入 session.jsonl 会话历史(持久化在磁盘,随分享/归档扩散)
安装插件后:三条通道全拦截
Pi Sentinel 接入 pi 的四类扩展钩子(下文流程中的 B 编号):
| 钩子 | 拦截点 | 作用 |
|---|---|---|
| B1 | input(用户输入) |
发送给 LLM 前 tokenize |
| B2 | tool_result(工具输出) |
返回对话前 tokenize + 落盘兜底 |
| B3 | message_end(助手消息完成) |
写入历史前兜底过滤 |
| B5 | tool_call(工具调用发出) |
token 还原为明文,工具拿到真实值 |
你粘贴日志(含手机号 / API key)
│
▼ B1 入向钩子
┌──────────────────────────────────────────────┐
│ 检测管线(45 规则,五道阈值闸) │
│ 策略决策(类型默认 / 规则覆盖 / floor 保护) │
│ 替换执行 │
└──────────────────────────────────────────────┘
│
▼
LLM 看到 "订单 13900000001 key=<API_KEY:r_001>"
│
▼ LLM 生成工具调用(用 token 指挥)
│
▼ B5 出向钩子:token → 明文还原
工具收到含真实手机号的 SQL ✓ 任务可执行(明文只到工具进程,不进上下文/历史)
│
▼ B3 落盘钩子:历史写入前兜底过滤
session.jsonl: 零完整明文 ✓ P1 不变量
流程中所有号码均为虚构演示值(139-0000-0001 风格),非真实号码。
示意流程刻意不展示任何真实号码——毕竟这是款隐私防护插件 ;)
安装
# pi 扩展安装(二选一)
pi install npm:@fanchaozz/pi-sentinel # npm 包(推荐)
pi install git:github.com/fanchaozz/pi-sentinel # GitHub
# 或手动 clone 到扩展目录
git clone https://github.com/fanchaozz/pi-sentinel ~/.pi/agent/extensions/pi-sentinel
快速开始
# 1. 重启 pi,输入任意含敏感值的文本试试(下例为虚构演示号)
联系我 139-0000-0001 # → 联系我 <PHONE:p_001>
# 2. 查看状态
/sentinel # 状态面板:token 数 / 存储大小 / 类型分布
# 3. 管理规则
/sentinel:policy # 45 条规则列表
# ↑↓ 移动 · Space 启/禁 · Enter 循环动作 · e 派生配置 · n 新增自定义
# 4. 查询已 token 化的值(用户特权)
/sentinel:query # 列表 + Enter 看全文 + / 搜索
配置
配置文件:~/.pi/sentinel/config.json(可用 SENTINEL_HOME 环境变量重定向)
{
"policyOverrides": { "phone": "derive" },
"customRules": [
{
"id": "vip-member",
"label": "VIP 会员号",
"type": "customer_id",
"match": { "kind": "regex", "pattern": "VIP\\d{8}" },
"strength": "mid",
"action": "derive",
"derive": { "op": "mask", "args": "3:4" }
}
],
"disabled": ["entropy:value"],
"ruleActions": {
"regex:cn-phone": { "action": "derive", "derive": { "op": "mask", "args": "3:4" } },
"regex:known-token:github-pat": { "action": "allow" }
}
}
动作语义:
| 动作 | LLM 看到 | 原值存储 | 出向还原 | 典型用途 |
|---|---|---|---|---|
tokenize(PII 默认) |
13900000001 |
✅ 加密 | ✅ | 脱敏且工具可用 |
redact(凭据默认) |
<API_KEY:r_001> |
❌ | ❌ | 高危凭据 |
derive |
13900000001|138****0000 |
✅ | ✅ | 脱敏展示 + 可逆 |
allow |
原文 | ❌ | — | 显式豁免 |
派生算子(4 种,全部类型无关):
| 算子 | 参数 | 示例 |
|---|---|---|
mask |
3:4(留头尾)/ !6:4(遮两端留中段)/ #*#*(等长模板) |
138****0000 |
length |
无 | 11 |
hash |
short / full | a3f5e2… |
regex_extract |
捕获组正则 | @(.+)$ → example.com |
TUI 反向推导:表单里填原文 + 期望输出,Enter 自动算出参数——
手机号 + 期望 138****8000 → mask(3:4)
身份证号 + 期望 ******出生日期段**** → mask(!6:4)
user@example.com + 期望 example.com → regex_extract(@(.+)$)
命令参考
| 命令 | 说明 |
|---|---|
/sentinel |
状态面板(任意键退出) |
/sentinel:policy |
规则管理面板(45 内置 + 自定义) |
/sentinel:policy set <type> <action> |
类型级覆盖(headless/脚本) |
/sentinel:policy reset <type> / reset-all |
恢复默认 |
/sentinel:query |
查询 store 中的值(默认脱敏,Enter 全文) |
/sentinel:query <token> |
直接查指定 token 全文 |
/sentinel:reset |
清空 store + 计数器(需确认) |
策略面板键位:↑↓ 移动 · PgUp/PgDn 翻页 · Space 启/禁 · Enter 循环动作 · e 派生配置(内置)/ 编辑(自定义)· n 新增 · x 删除 · r 重置覆盖 · / 过滤 · q/Esc 退出
查询面板键位:↑↓ 移动 · Enter 全文/脱敏切换 · / 搜索 · Esc 退出
存储与安全
~/.pi/sentinel/
├── store/<sessionId>.jsonl # 原值(AES-256-GCM 加密)
├── config.json # 你的策略配置(无明文)
└── audit/<sessionId>.jsonl # 审计日志(含证据哈希,无明文)
- 原值只存在于加密 store,密钥派生自设备密钥
- 派生事实(
138****0000)可明文落盘——它不是原值 password/private_key受 floor 保护,不可低于 redact- 已知边界:用户授权派生后,LLM 理论上可从多个派生值拼凑逼近原值(派生不设防原则);插件保证完整原值本身不出现
隐私声明
- 所有检测/加密/存储 100% 本地,无任何网络行为
- 不收集遥测
- 审计日志仅含哈希指纹
开发
npm install
npm test # vitest 125 用例
npm run typecheck # tsc 零错误
设计文档:DESIGN_v1.0.0.md(架构/数据流/验收标准完整版)
License
MIT