Skip to content

脱敏与拦截

凭据明文出了金库,下一道闸门是 Hook。本篇讲它在做什么、它的边界。

两个拦截点

AI 客户端                                                Sigil
   │                                                       │
   │  tools/call: http_request(GET api.github.com/...)     │
   ├──────────────────────────────────────────────────────►│
   │                                                       │
   │                       ┌───────────────────────────────┴───┐
   │                       │ PreToolUse Hook                   │
   │                       │   - 解密 github-personal           │
   │                       │   - 注入 Authorization: token xxx  │
   │                       │   - 记录"将要执行"                  │
   │                       └─────────────┬─────────────────────┘
   │                                     │
   │                                     ▼
   │                       (真实 HTTP 请求送到 api.github.com)
   │                                     │
   │                                     ▼
   │                       (响应回来:可能含 ghp_xxx 残影)
   │                                     │
   │                       ┌─────────────┴─────────────────────┐
   │                       │ PostToolUse Hook                   │
   │                       │   - 正则扫描响应                    │
   │                       │   - 匹配凭据模式 → 替换 [REDACTED]   │
   │                       │   - 记录"已执行完成"                 │
   │                       └─────────────┬─────────────────────┘
   │                                     │
   │  result: { ..., "token": "[REDACTED:github_token]", ... } │
   │◄────────────────────────────────────────────────────────  │

两道闸门都在 Sigil 进程内——AI 客户端看不见它们存在。

PreToolUse:注入

调用方说"我要发 HTTP 请求"——Sigil 在请求真的出去之前:

  1. 解析目标 URL(host + path)
  2. 根据 host 匹配凭据:
    • api.github.com → 找 type=github_token 的凭据
    • gitee.com/api → 找 gitee_token
    • 内部 API host → 用模板里指定的 http_api 凭据
  3. 校验 Scope Policy
  4. 从 OS 密钥环解出明文(短暂在内存,zeroize 包装)
  5. 注入到请求头/参数
  6. 真的发出请求
  7. 明文离开作用域自动清零

明文存活时间 < 1 ms。客户端从来没看到。

PostToolUse:脱敏

请求成功回来后,响应里有可能含凭据残影——比如:

  • GitHub API 把 Token 的最后 4 位 echo 回来
  • 某个 webhook 配置返回 secret: xxx
  • 错误信息里包含完整 URL(含 query string 中的 key)

Sigil 不假设这些情况都不会发生。PostToolUse 用三层过滤:

第一层:已知凭据模式

对当前金库中所有凭据明文做一次模式扫描——如果响应里出现了任何凭据的明文,立即替换为 [REDACTED:credential_<id>]

这是最严格的一层:是基于事实(你金库里真有的明文)而非启发式("看起来像 Token")。

第二层:通用凭据模式

常见格式的正则匹配:

模式替换为
ghp_[A-Za-z0-9]{36}[REDACTED:github_token]
github_pat_[A-Za-z0-9_]{82}[REDACTED:github_fine_grained]
gho_[A-Za-z0-9]{36}[REDACTED:github_oauth]
gitee_[A-Za-z0-9]{32}[REDACTED:gitee_token]
sk-[A-Za-z0-9]{48}[REDACTED:openai_key]
sk-ant-[A-Za-z0-9-_]{95,}[REDACTED:anthropic_key]
xoxb-[A-Za-z0-9-]+[REDACTED:slack_bot_token]
MySQL/Postgres 连接串里的 :password@[REDACTED:db_password]
AWS Access Key (AKIA...)[REDACTED:aws_key]

完整规则列表内置在 Sigil,随版本更新。

第三层:通用秘密启发式

最后兜底,检测:

  • 长度 ≥ 20 的高熵字符串
  • 紧跟 password= / token= / secret= / key= 的值
  • Bearer 鉴权头

这一层会有误报(普通的长 UUID 也可能被打掉)。Sigil 用统计阈值控制:误报率高时会降级为只标注不替换,让用户决定。

脱敏的诚实

三层脱敏不构成 100% 拦截承诺

正则与启发式有天然边界:

  • 自定义格式的内部 Token(公司自创 schema)
  • Base64 编码后嵌入更长字符串的凭据
  • 多次拼接 / 加密后的"派生形态"
  • 通过侧信道(响应长度 / 时间)泄露的元信息

Sigil 的脱敏是降低风险而非消除风险。配合 审计日志 的事后追溯 + Scope Policy 的事前限制,整体安全水平才是合格的。

客户端层面的接力

脱敏只在Sigil 出口生效。结果到了 AI 客户端之后:

  • 客户端可能把结果存进会话历史(用户可见)
  • 客户端可能上传给 AI 厂商做推理(厂商策略决定)
  • 客户端可能在 UI 里展示完整 JSON(截图风险)

这些是 Sigil 范围之外的——但合规做法是:

  • 主流 AI 厂商(Anthropic / OpenAI)都有 API 数据使用策略
  • 客户端可以配置"工具结果不进训练数据"
  • 用户可以在 Sigil 设置里限制"哪些能力允许返回大段文本"

误报与豁免

误报场景例:

  • 你查 git log,commit hash 是 SHA-1(40 字符 hex)—— 被启发式打掉
  • 你查 JIRA issue,ticket key 紧跟 key=PROJ-123 —— 被启发式打掉

Sigil 给出两个豁免机制:

  1. 能力级豁免:在能力配置里标"不做启发式脱敏"(如 git_query)—— 这种能力的返回值通常是结构化的,已知不含凭据
  2. 域名级豁免:标"这个域名的响应不脱敏"(如自家 status page)

豁免只对启发式(第三层)生效——前两层(已知明文匹配、已知模式匹配)任何时候都不豁免。

Hook 永远不可旁路

Hook 是 Sigil 内置的,不是可插拔的脚本。理由:

  • 可插拔脚本意味着可以禁用
  • 第三方脚本意味着审计无法覆盖
  • 这是安全防线,不是用户偏好

如果未来需要"用户自定义脱敏规则",会以追加规则的形式(不能删除内置)实现。

测试自己的脱敏

Sigil 内置一个"脱敏自检"工具(设置 → 安全 → 脱敏自检):

  1. 把任意一段文本贴进来
  2. 模拟运行三层 Hook
  3. 看输出(红色高亮被打掉的部分)

用来快速验证"我担心的某种泄漏路径,Sigil 是否能拦住"。

下一步

让 AI 帮你干活,但永远拿不到你的密钥