别让模型复读你的密钥:日志脱敏这道闸,两头都得有

上个月,我给推理服务做的值班机器人开始大受欢迎:客户报障,它就拉生产日志相关行喂给模型,让模型给出诊断。直到有一天,一份诊断 markdown 里原样出现了一串 32 位 token——那是同事启动时 dump 环境打印的 API key,一直在日志里躺着。

专属插画
别让模型复读你的密钥:日志脱敏这道闸,两头都得有

别让模型复读你的密钥:日志脱敏这道闸,两头都得有

上个月,我给推理服务做的值班机器人开始大受欢迎:客户报障,它就拉生产日志相关行喂给模型,让模型给出诊断。直到有一天,一份诊断 markdown 里原样出现了一串 32 位 token——那是同事启动时 dump 环境打印的 API key,一直在日志里躺着。

当时 key 还没失效,诊断 markdown 已经发进了对外群。我 30 秒内轮换了 key,但那份 markdown 是明文,还被同步进了内部仓库,能想到的泄露渠道得挨个清了一遍。

复盘会上最扎心的一点:那条环境 dump 日志躺在生产日志里已经有三周了,之前每次人工排查都直接翻过它,每个人都「看到」了它,但没有任何一个环节把它当成风险。风险一直存在,只是没有一道机器闸门拦住它——靠人眼「看到了就绕开」,等于没有防护。

洞在哪里

事故前的链路有两个前提,全错了。

第一,「模型是人类,会动脑子」:读日志时它会自己判断哪些不该复述。不是的。模型的任务是把上下文里看到的东西写进回答,而一串 32 位密钥恰好是它最擅长照抄的格式。我事后专门测过:放一条带密钥的日志,跑五次,五次全文复述,一次不多。

第二,「输入端清洗就够了」。我们其实做过一次正则脱敏,但只覆盖 sk- 前缀和 Bearer 头。日志里还有一类十六进制 token 和一个内网数据库 DSN,全没被拦下。

整改:四层硬闸

后来把脱敏拆成四层,任何一层命中就拦截,没有人工跳过这个口子:

1. **正则清单(输入端)**:sk-、AKIA、JWT(eyJ 开头)、MySQL/Postgres 连接串、`password=`/`token=` 键值对。每发现一种新密钥,当天补规则,进事故记录就当天进清单。

2. **高熵启发(输入端)**:对长度 ≥ 20 的连续字符串段算香农熵,超阈值不直接删,标出来走人工复核。十六进制 token、base64 编码的密钥是正则最容易漏的形态。

3. **输出回扫(输出端)**:模型产出的诊断 markdown 在发出前,再过一遍同一套规则。命中直接拦截,同时报警到值班群。这一层才是真兜底——上线头两周两次真实拦截(一次密钥、一次手机号)都拦在输出端,手机号嵌在普通句子中间,输入端根本没把它当回事。

4. **字段白名单(源头端)**:日志抓取工具不再拉「最近 500 行」,改成只拉指定字段:时间戳、级别、服务名、错误码、消息体。环境 dump 那一行从此进不了模型的视野。

误报也交了学费:熵值规则头两周误报了 40 多条,一半是 trace ID 这种天然十六进制。后来加了边界检查(前面必须是空白或标点),误报降到一周一条以内,人工复核的量可以接受了。

怎么证明闸门真的在工作

管道上线那天,我把事故前的日志切了一份 fixture(密钥换成假值)写成回归用例:以后每次改脱敏规则,先跑一遍 fixture,断言输出里所有原密钥对应的高熵段都必须被打码。

这有两层用意。一是防回归:规则越长越容易出交互 bug,有一次加 JWT 规则时,`eyJ` 前缀匹配没兼容带引号的 JSON 日志,fixture 当场红掉,才发现。二是信任:后面接这个管道的人不用信我说的「四层都生效」,跑一遍 fixture 自己看结果。

顺带一个成本提醒:回扫是同步跑在出图路径上的,用的是本地规则引擎而非外部大模型,单条诊断增加的延迟不到 200 毫秒。如果换成调外部模型审输出,延迟和成本都会翻好几倍。

收尾两句

- 别信模型的「自觉」。只要你希望某类文本「绝对不能出现在输出里」,就在管道层做:输入脱敏 + 输出回扫,缺一头,闸门就是摆设。

- 真实泄露是低概率事件,但它一定会发生,问题只在于管道有没有「最后一道拦截」。有的话,成本是几个小时的改造;没有的话,成本是一次公开事故和一批要轮换的密钥。

留言区

欢迎分享你的想法!

发表留言

0/500

加载留言中…