Rehydar
Rehydra OpenCode plugin — anonymizes secrets in LLM context using native plugin hooks
0
587
近 7 天 587
37.0
生态多维模型
4 天前
2026-09-30
快速安装与配置
opencode.json写入当前项目的 opencode.json,只对这个仓库生效。
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-rehydar@0.14.3"]
}写入 ~/.config/opencode/opencode.json,对所有项目生效。
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-rehydar@0.14.3"]
}若你要在本地改造这个插件,先装到项目里再从本地路径引用。
shell
pnpm add -D opencode-rehydarOpenCode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。
为 AI 工作流、编程助手和浏览器场景提供 PII 安全防护。
检测、替换、加密,并在需要时还原真实值。
npm i opencode-rehydra-core · npm i opencode-rehydar
问题所在
当你把代码、消息或文档发给 LLM 时,真实姓名、邮箱和 API Key 会一并离开你的机器。已有的匿名化工具要么永久删除 PII(那样对话就断了),要么只是临时遮盖(那样模型无法推理)。
你需要的是化名:模型能拿来推理,你的工具又能还原回去。
Rehydra 的做法
- 检测 — 正则捕获结构化 PII(邮箱、电话、英国邮编、IBAN、信用卡卡号)。本地 NER 模型(ONNX,不联网)捕获非结构化 PII(人名、机构、地点)。
- 替换 — 每个 PII 值获得一个稳定占位符:
<PII type="PERSON" id="1"/>。模型用占位符而不是真实数据来工作。 - 持久化 — 同一实体在整个会话的每条消息里都是同一个 ID。模型能维持关系一致性,却从未看到真实 PII。
- 还原 — 当响应需要变回真实值时(写文件、执行 bash 命令、给出最终答案),Rehydra 从加密映射中还原原始值。
快速开始
OpenCode 插件
npm install opencode-rehydar
在 opencode.json 中启用(V2 使用 plugins 列表):
{
"plugins": [
{
"package": "opencode-rehydar",
"options": { "envFiles": [".env", ".env.local"] }
}
]
}
它拦截 OpenCode 与 LLM 之间的对话。.env 文件中的密钥在离开本机前被替换为占位符,在工具执行前被还原。需要 OpenCode 的 V2 插件 API;不支持 V1 的 plugin 配置和 hook 集合。
插件会脱敏会话标题请求(session.hook("title")),但只对主回答还原响应(http.response 且 kind === "primary")。因此会话标题可能显示 <PII .../> 占位符,建议禁用标题 agent 以避免占位符标题,详见限制与配置。
作为库嵌入你的应用
上面实现背后的可高度定制内核。支持自定义 NER 模型、加密密钥提供器、会话存储提供器、标签格式等多种调整。
import { anonymize } from 'opencode-rehydra-core';
const { anonymizedText } = await anonymize(
'Email john.smith@acme-corp.com or call John at +41 79 123 45 67'
);
在 Node.js、Bun 和浏览器中都能运行。数据不离开你的机器。
代理模块
SDK 内置 rehydra/proxy 模块,可嵌入你自己的服务。它在出站消息上做脱敏,并在工具调用执行前还原参数,因此模型用占位符工作,而你的工具始终拿到真实值。
import { createRehydraProxy } from "rehydra/proxy";
// 将 createRehydraProxy({ ... }) 挂载到你自己的宿主上。
工具结果(文件读取、bash 输出)在返回时脱敏。工具调用参数(文件写入、bash 命令)在执行前还原。
为什么选 Rehydra
可逆,而非破坏性
多数 PII 库做的是永久遮盖或删除。Rehydra 用 AES-256-GCM 加密原始值,按需还原。给模型用匿名值,给工具用还原值——完整往返,而不是单行道。
跨会话稳定的身份
PERSON_1 在整段对话的每条消息里都是 PERSON_1。当 John Smith 分别出现在第 1、5、20 条消息里,模型每次看到的都是同一个占位符。它能追踪关系、引用前文、产出连贯的多轮输出——而从未看到真实 PII。会话可持久化到 SQLite(服务端)、IndexedDB(浏览器)或内存,身份映射在重启后依然有效。
零信任,端侧运行
NER 推理通过 ONNX Runtime 在本地执行(量化模型约 280 MB)。不调用任何外部 API。可离线工作。PII 永不离开你的机器。
关键能力
- 密钥而不只是 PII:除姓名、邮箱、电话外,还能检测 API Key(OpenAI、Anthropic、Stripe、AWS、GitHub 等)、JWT、私钥、数据库连接串和
.env密钥。 - 面向流式:专为 LLM token 流设计。带句子缓冲的分块系统配合 NER 重叠保留,确保 PII 跨分块边界时仍能准确检测,并提供低延迟实时流式模式。
- 服务端工具循环:
createRehydraFetch支持在stream: true的 OpenAI Chat Completions 与 Anthropic Messages 请求上使用onToolCall。首次响应被缓冲,其完整工具调用在回调执行前完成校验。交错调用保留各自的 ID 和参数。工具参数在本地还原,工具结果在下一次请求前脱敏。续请求使用stream: false,最终响应以兼容 provider 的 SSE 返回,包含完成事件和用量信息;代价是缓冲了首包和最终 JSON,因此不保留逐 token 的生成延迟。不含工具调用的流保持原始事件。maxToolRounds限制回调轮数(默认 10 轮),maxToolResponseBytes限制每个缓冲响应大小(默认 8 MiB);达到轮数上限时未执行的调用原样返回给调用方。通过 fetch 传入AbortSignal可取消缓冲或阻止后续回调,已在运行的回调需自行处理取消。被截断的调用、畸形参数和超限流会在工具执行前失败。上游 HTTP 错误保留其状态码。OpenAI 工具循环要求只有一个 completion choice。自定义 provider 可通过LLMContentProvider上的streamingToolLoop接入。 - 机器翻译的语义增强:PII 标签上的可选性别和范围属性(
<PII type="PERSON" gender="male" id="1"/>、<PII type="LOCATION" scope="city" id="2"/>)为下游系统保留语法上下文。 - 稳定的外部标签 ID:跨独立匿名化调用管理 PII 映射的调用方,可以通过
existingPiiMap预置小写字母数字 ID。这允许 ID 由被遮盖的值派生(例如用带密钥的 HMAC),而不依赖每次调用的计数器。
const existingPiiMap = new Map([
['EMAIL_a4f2c9d8e7b6q', 'alice@acme.com'],
]);
const result = await anonymizer.anonymize(
'Email alice@acme.com',
undefined,
policy,
existingPiiMap,
);
// → 'Email <PII type="EMAIL" id="a4f2c9d8e7b6q"/>'
外部 ID 必须匹配 [0-9a-z]+。纯数字 ID 保留现有的数字行为。避免以识别器前缀开头(case、file、ref、ticket)的 ID,也避免连续七位或更多数字——这些形态本身可能在传入的标签里被识别成 case ID 或电话号码。
示例:带会话的完整往返
import {
createAnonymizer,
InMemoryKeyProvider,
SQLitePIIStorageProvider,
} from 'opencode-rehydra-core';
const keyProvider = new InMemoryKeyProvider();
const anonymizer = createAnonymizer({
ner: {
mode: 'quantized', // ~280 MB 模型,首次使用时自动下载
caseFallback: true, // 检测 "tom" 这类小写名字
thresholds: { PERSON: 0.8 }, // 对人名要求更高置信度
onStatus: console.log, // 打印模型下载进度
},
semantic: { enabled: true }, // 为机器翻译添加性别/范围属性
secrets: { enabled: true }, // 检测 API Key、JWT、连接串
keyProvider,
piiStorageProvider: new SQLitePIIStorageProvider('./pii.db'),
});
const session = anonymizer.session('chat-123');
// 消息 1 — NER 检测人名和机构,正则捕获邮箱
const r1 = await session.anonymize(
'Tell John Smith at Acme Corp (john.smith@acme-corp.com) we accept the offer'
);
// → "Tell <PII type="PERSON" gender="male" id="1"/> at <PII type="ORG" id="2"/>
// (<PII type="EMAIL" id="3"/>) we accept the offer"
// 消息 2 — 同一实体跨消息保持 ID
const r2 = await session.anonymize(
'CC john.smith@acme-corp.com and loop in admin@acme-corp.com'
);
// → "CC <PII type="EMAIL" id="3"/> and loop in <PII type="EMAIL" id="4"/>"
// 还原任意消息 — PII 映射会自动从 SQLite 加载
const original = await session.rehydrate(r2.anonymizedText);
// → "CC john.smith@acme-corp.com and loop in admin@acme-corp.com"
await anonymizer.dispose();
地址识别说明
默认正则注册表检测英国邮编语法和带门牌号的英文街道地址,例如 34a Friskin Road。当公寓标识与街道地址相邻时会被包含,例如 Flat 1\n34a Friskin Road。以邮编结尾的连续英国地址块会被整体遮盖为一个 ADDRESS;独立的邮编使用 POSTAL_CODE。裸公寓号、无门牌号的建筑以及其他国家的邮编格式需要自定义识别器或 NER。邮编匹配只检查语法,不验证该邮编当前是否仍在使用。
通过检测策略禁用 ADDRESS 或 POSTAL_CODE 即可关闭对应识别器。
包
| 包 | 说明 |
|---|---|
opencode-rehydra-core |
核心 SDK —— 检测、匿名化、还原 |
opencode-rehydar |
OpenCode 插件 —— 在密钥到达 LLM provider 前脱敏 |
rehydra-pi |
Pi 扩展 —— 通过 ExtensionAPI 在 Pi 对话中保护 PII |
文档
License
同类生态推荐
Provider
@tinfoilsh/opencode-provider
Verifiably-private models from Tinfoil secure enclaves, for the opencode coding agent
Vibeguard
opencode-vibeguard
OpenCode 插件:参考 VibeGuard 的占位符格式(__VG_...__)在发送给 LLM 前脱敏、在响应完成后自动还原、在工具执行前还原参数
Oh My Opensecret
oh-my-opensecret
OpenCode 隐私脱敏插件:自动识别并替换敏感信息,保证 LLM 提供商看不见明文,同时本机工具执行不受影响