安全

安全模型与残余风险

Tokenhush 是一个本地、仅回环的 HTTP 网关,在机密与 PII 到达模型之前完成脱敏。作为安全工具,它首先要可以被检视。本页说明回环网关能作用于什么、开源核心强制的八条不变量、它如实记录而不是隐去的九项残余风险,以及究竟什么内容会落盘。

诚实的说法是:高置信拦截,并把边界一并写下来——而不是「什么都不会外泄」的保证。

网关能作用于什么

Tokenhush 运行在你的机器上,位于一个 AI 编程工具与模型提供商之间,通过你指向本机回环的 base URL 接入。经过这条路径的请求,才是它能处理的请求——覆盖范围与这条路径的宽度完全一致。

构建性防护

  • 仅绑定回环。 服务绑定 127.0.0.1,并在主机具备 IPv6 回环时绑定 [::1];非回环绑定不可能成功。Host 允许清单始终生效, 浏览器式请求会做 Origin 校验。
  • 一个控制端点、一个凭据。 控制面恰好是GET /status,要求每次运行生成的 bearer token(以0600 权限存储)。不存在修改允许清单的端点;非 GET 请求返回 JSON 405 与 Allow: GET,其他 GET 返回 404。
  • 故障关闭,而非故障开放。 检测器失败时拒绝该请求,而不是未脱敏地 转发;携带非 identity Content-Encoding 的请求在读取 body、 打开任何上游连接之前以 415 拒绝。
  • 确定性检测。 六个检测器类别——prefix、jwt、pem、luhn、email,以及 按需启用的 entropy——全部是规则。不调用模型,不做语义猜测; 六者中五个默认开启。

明确在路径之外

  • 不接受 base URL 的工具。 Cursor agent 流量、ChatGPT 与 Claude 桌面应用、浏览器 web UI 都不提供 base URL 设置;要覆盖它们需要配合根证书的 系统级拦截,而开源核心未实现。
  • 编码后的 secret。 base64、hex 或 URL 编码后才离开的 secret 不会被检测:本次重写不设 normalization 传递。对应下方残余风险R1。
  • 键位置的 secret。 遍历只读取值,因此作为 JSON 对象键使用的 secret 会原样转发。对应下方 R2。
  • 响应路径的替换。 响应作用域的规则可以放行、告警或阻断,但绝不 替换;试图脱敏响应的规则在编译时即被拒绝。
  • 工具自身的遥测。 一些工具会向 PostHog、Sentry 之类的服务发送自己的遥测;这些端点绕过 base URL 改写,不在网关路径上。

八条硬性不变量

八条全部在代码中强制,并由开源核心中的具名测试钉住。它们是本设计不会弯折的规则。

  1. 绝不向出站回填占位符。 出站 writer 只持有正向映射(secret → 占位符),没有反向映射,也没有还原方法。重复应用脱敏是幂等的,已携带已知占位符的 body 原样转发。TestInvariant1NeverBackfillOutbound
  2. 请求/响应明文不落盘;只落元数据。 审计接缝只有一个写方法,只写元数据 记录;默认 sink 是 no-op,不持久化任何东西。运行级守卫驱动真实二进制,扫描该次运行 可能写入的一切。TestInvariant2NoPlaintextOnDisk
  3. 默认无根证书、无 MITM。 Tokenhush 从不终止 TLS。absence 守卫扫描生产源码中证书安装或 MITM 路径所需的每一种形状——TLS 监听器、密钥对、 证书池、内嵌证书——一旦出现即失败。TestAbsenceNoCertificateInstallPath
  4. 仅回环绑定、Host 与 Origin 校验、控制 bearer token。 非回环绑定 不可能成功;外部 Host 被拒绝;跨源请求被拒绝;控制面缺少凭据返回 401,错误凭据返回 403。TestInvariant4LoopbackOnly
  5. 检测失败时故障关闭,而非故障开放。 规则 panic、超时、超出 span 预算或返回畸形发现时,产生带正确原因的标签化拒绝——绝不静默放过。每次失败恰好写一条 仅含元数据的记录;超过字节预算的叶会写一行仅含元数据的 stderr,而不是静默通过。TestInvariant5FailSafe
  6. 恰好两个可开关的厂商出口类别,且命令限定。产品只可为两种已披露用途触达厂商,且只在你未设置对应开关之前。第二个守卫证明披露 恰好声明两个类别、各自命名其开关,且每个开关在任何网络调用之前短路。TestInvariant6NoVendorEgress
  7. Content-Encoding 故障关闭。 携带非 identityContent-Encoding 的请求在读取 body 之前以 415 拒绝。响应方向先解码 gzip 与 deflate 再提交任何状态码;网关无法解码的编码以 502 拒绝并丢弃上游字节。 不存在未检查的透传。TestInvariant7EncodingFailClosed
  8. SSE 感知的有界回填(256 KiB)。 SSE 流中未终止的 token 永远不能让回填 writer 持有超过 SSEBackfillHoldbackBytes(256 KiB);越过界限后最旧的数据按字面顺序释放。不丢字节,也不伪造字节。TestInvariant8BoundDerived

九项残余风险

这些风险被如实记录,而不是被隐去;列出它们并不等于声称它们不存在。下表与安全模型的 登记逐条一致,包括那些公开起来并不舒服的条目。

当前公开安全模型中的残余风险 R1–R9。
#残余风险实际含义状态
R1编码后的 secret 不会被捕获base64、hex 或 URL 编码后才离开的 secret 不会被检测:本次重写有意不设 normalization 传递,且非 identity 的请求 Content-Encoding会以 415 拒绝,而不是为检测而解码。JSON 字符串转义在两个方向都已处理,因此残余仅是内容编码,绝不是 JSON 转义。已记录并接受
R2JSON 对象键中的 secret 不会被捕获放在对象键而不是值里的 secret 会原样转发:叶遍历不提供键叶,因此不存在 半可用的键路径。sensitive_keys块脱敏的是命名键的值,绝不是存放在键里的 secret;两种同形仍不匹配: 敏感键的值是容器,以及 Kubernetes/Docker-env 同形——name 字段携带键名、value 字段携带 secret。已记录并接受
R3签名远程包可能通过自身 allowlist 弱化检测签名远程包可以通过其全局或每条规则的 allowlist 压制匹配:非弱化 floor 对 allowlist 保持中立、不检查 allowlist,而规则签名密钥仍是信任根。弱化检测 的包,是签名密钥持有者选择发布的包。已记录并接受
R4command 规则拒绝处于休眠,且 manifest v2 升级不向后兼容command 规则拒绝保持休眠,直到下一个 schema 闸门打开,而闸门只在规则 manifest schema_version 达到 2 时打开。该升级与钉住== 1 的客户端不兼容,因此后端必须继续提供 v1 manifest,直到旧版本线退出支持。已记录并接受
R5响应路径 body 不受请求侧 body 护栏约束响应 body 不受请求侧护栏限制:response_buffer_bytes(默认 32 MiB)是整段缓冲响应的一个独立总量上限,超过它的响应在提交前返回 502。在该上限放行的 body 内部,响应作用域检测器仍最多扫描自己的每原始 检测器预算,超过预算的叶会被截断,且没有 stderr 报告、没有计数器——因此 响应作用域检测仍可能漏掉内容。已记录并接受
R6跨 SSE JSON 信封 data: 事件拆分的占位符仅部分还原当每个事件都是完整 JSON 信封时,内容层拆分会被精确还原一次,content 通道与 tool call arguments 通道皆然,匹配在解码域进行。剩余边界:若上游把 JSON 文档本身撕开,只有原始拼写可知;而一个非匹配通道的事件在两半之间 到达时,被扣留的两半按字节原样释放,占位符片段以字面形式送达、不被还原。 token 未解析期间,仅受影响通道被扣留,直到 token 解析或联合 256 KiB 预算触发。部分关闭;剩余项已记录并接受
R7按需启用的 high_entropy 检测器会替换合法 base64 载荷entropy 默认关闭,因为合法的高熵载荷与 secret 在构造上无法区分:启用后,请求中的 base64 图片会在抵达上游之前被替换成单个占位符,模型因此看不到该图片。仅在不携带 图片、data URL 或长随机标识符的工作负载上启用,或接受这次替换。已记录并接受
R8全局 blocklist 并非在每条求值路径上都被应用有两条决策路径不应用全局 blocklist,只有编译后的求值路径运行它。每个叶在该 路径上保持 Block 优先,但通过轻量路径决策的调用方不会得到 blocklist 的应用。这一点被记录而不是被修复,钉住该行为的测试位于编译路径上。已记录并接受
R9请求 body 护栏是内存墙,且第二次遍历不受保护等于或低于 max_body_bytes(默认 64 MiB)的 body 在声明 JSON 的路径上要被遍历两次,而只有第一次遍历运行在检测器超时之内; Forwarder 内的第二次遍历不受保护、没有超时。每次遍历都会复制叶值,因此 峰值内存是 body 的一个小倍数,而不是 body 本身。超过上限的 body 仍以 403 body_too_large 拒绝,现在附带一行仅含元数据的 stderr。已记录并接受
状态值取自安全模型本身。每一条都是本设计的已知边界,保持可见,让运维者能带着完整图景 做判断。

磁盘上写什么

除元数据外不持久化任何东西。数据目录下会写入四条路径,其中没有任何一条携带请求或响应 内容。

<DataDir>/run.json
pid、port、addrs、started_at。 绝不含控制 token。权限 0600,原子写,干净退出时删除。
<DataDir>/control.token
会话控制 bearer token。权限 0600,原子写,干净退出时删除。
<DataDir>/rules/…
已验证的规则缓存与防回滚标记:签名文档与序列号,不含请求内容。
<DataDir>/update/highwater.json
更新防回滚标记:一个 serial/version 整数,别无其他。

永不落盘:body、secret 与映射。

请求与响应 body、检测出的 secret、以及占位符到 secret 的映射,永不写入磁盘。脱敏日志行经过掩码、只写 stderr、永不持久化;响应侧的还原 计数器同样只写 stderr。由于脱敏每轮都会重新扫描整段携带的对话,这些计数器随携带的 出现次数增长——这是仅含元数据的工作,绝不持久化任何 body。

密钥处理:在内存中可见、不落盘

密钥经过网关以完成工作;网关不存储它们。

  • 提供商密钥只在内存中透传。你的工具持有自己的提供商密钥;网关仅为其正在处理的请求读取并转发,不存储。检测、 替换与还原都在同一进程内完成。
  • 映射限定于本次会话。 secret 到占位符的映射在会话期间仅存在于内存; 重启网关即丢弃——占位符可能原样送达你的工具,这是可见的降级,而不是静默失败。异源 占位符按字节原样返回,绝不被猜测。
  • 两种凭据,两种处理。 唯一写入磁盘的凭据是每次运行生成的控制 token(control.token,权限 0600,干净退出时删除)——它是回环控制 凭据,不是提供商密钥。

网络外发:两个命令限定的类别

Tokenhush 发起的厂商请求只有两个类别,各自限定于触发它的命令,且各自可关。两者都发往updates.tokenhush.com,保留期 30 天。

更新检查只在 self-managed 安装上运行 tokenhush update(含--check)时发生;规则同步只在运行 tokenhush rules sync时发生。设置对应开关后,每个类别都会在发起任何网络请求前返回——TOKENHUSH_NO_UPDATE_CHECK=1与 TOKENHUSH_NO_RULE_SYNC=1;tokenhush privacy会打印这份披露。

  • 更新检查

    已生效

    当前构建只在你运行对应命令时发送这些请求;下列字段、保留期与关闭方法均适用。

    检查已签名的版本更新:更新清单、其签名撤销文档,以及根签名 key-list。 三者共用同一 host 与同一开关,属同一外发类别,而非三项。

    Host
    updates.tokenhush.com
    生效时默认
    enabled
    关闭方法
    更新检查只在 self-managed 安装上运行 tokenhush update(含 --check)时 发生;Homebrew 与 Scoop 安装交由各自的包管理器处理。设置环境变量 TOKENHUSH_NO_UPDATE_CHECK=1 可使该命令在发起任何网络请求前直接返回。
    保留期
    Cloudflare 访问日志与请求记录保留 30 天。

    涉及字段

    • channel本机发送请求的发布通道(例如 stable 或 beta)。
    • os_arch本机发送运行中二进制的操作系统与 CPU 架构。
    • current_version本机发送当前二进制版本号,供服务端判断是否存在可用更新。
    • ip服务端可见请求的来源 IP 地址,服务端可见。
    • timestamp服务端可见服务端记录的请求时间戳。
    • access_logs服务端可见Cloudflare 访问日志(路径、时间、来源 IP、User-Agent)。
  • 规则同步

    已生效

    当前构建只在你运行对应命令时发送这些请求;下列字段、保留期与关闭方法均适用。

    获取已签名的检测规则清单与规则包,使本地检测器可增量收紧, 无需重新分发二进制。

    Host
    updates.tokenhush.com
    生效时默认
    enabled
    关闭方法
    规则同步只在你运行 tokenhush rules sync 时发生,绝不自动同步。 设置环境变量 TOKENHUSH_NO_RULE_SYNC=1 可使该命令拒绝同步且不发起任何网络请求。
    保留期
    Cloudflare 访问日志与请求记录保留 30 天。

    涉及字段

    • channel本机发送请求的规则通道(例如 stable 或 beta)。
    • os_arch本机发送运行中二进制的操作系统与 CPU 架构。
    • current_schema_version本机发送当前所用规则包的 schema 版本与 serial。
    • ip服务端可见请求的来源 IP 地址,服务端可见。
    • timestamp服务端可见服务端记录的请求时间戳。
    • access_logs服务端可见Cloudflare 访问日志(路径、时间、来源 IP、User-Agent)。

上表来自二进制随附的同一份清单,并以字段级形式同时呈现在隐私政策中。

报告漏洞

不要为漏洞开公开 issue。

请通过 GitHub 私有安全公告报告;详情只在修复发布后公布。披露政策见开源仓库中的SECURITY.md。

阅读 SECURITY.md打开私有安全公告