数据出域是一条单向的坡:本机、内网、供应商、公网,往上走容易,往下收不回来。 扫描规则一定有漏有误:十处敏感项抓九处、报十一条里两条误报,这已经是好结果。 熵检测不出密钥,也拦不住弱口令;真正拦住 hunter2 的是规矩,不是规则。
上一节讲「错了怎么看」,这一节讲「什么东西不该让它看见」。 两件事都在边界上,但这一件更硬:归因错了只是白干活,出域错了要改密码。 Demo 造一个九文件的仓库,埋十处敏感内容,跑五组扫描规则,再算一遍召回和误报, 最后看 .env 误提交之后历史里还剩什么。
一、数据出域的四个层次
先定义「出去了」是什么意思。它不是「上传」,是「离开你能控制的范围」。
| 层次 | 边界 | 典型内容 |
|---|---|---|
| ① 本机 | 不出这台机器 | 调试日志、临时缓存 |
| ② 内网 | 不出公司网络 | 内网地址、真实表名 |
| ③ 供应商 | 发到模型厂商 | 粘进对话的代码和报错 |
| ④ 公网 | 谁都看得见 | 公开仓库、贴出去的 issue |
四层是一条单向的坡:往上走容易,往下收不回来。 把一段日志粘进对话是第 ③ 层;同一段日志贴进公开 issue 就是第 ④ 层,而它再也回不到第 ③ 层。 判断某个东西能不能给 Agent 看,只需要问一句:这段话如果出现在公开 issue 里, 我会不会立刻去改密码。 答案是「会」,它就至少是第 ③ 层,得当成「已经离开过」来对待。 这一问好用的原因是它绕开了「重要不重要」这种主观判断。 「重不重要」可以争论,「要不要改密码」不能。
二、密钥与凭据
Demo 埋了十处敏感内容:三把密钥、三处个人信息、一个绝对路径、两个内网 IP,外加一个明文口令。 五组规则一共报了十一条,逐条对账之后:真九条、误报两条。
| 指标 | 值 | 含义 |
|---|---|---|
| 召回 | 9/10 | 十处抓到九处 |
| 准确 | 9/11 | 报出来的九成是真的 |
| 误报 | 2 | 图片片段一条、号码一条 |
两个误报各有来头,都值得看。
高熵那条命中的是一段 base64 图片——它确实「字符很杂」;手机号规则从身份证号里又抠出一个十一数字。
规则之间会互相重叠,所以误报几乎是必然的。
这决定了扫描结果的正确用法:它是「人工确认清单」,不是「直接删除清单」。
漏掉的那一条最值得说:.env 里写着 DB_PASSWORD=hunter2。
前缀规则不认它(没有 sk- 之类的前缀),高熵规则也不认它(七个字符,熵很低)。
| 串 | 长度 | 熵 | 高熵规则 |
|---|---|---|---|
| sk-live 密钥 | 40 | 4.33 | 过 |
| ghp_ 令牌 | 40 | 4.14 | 不过 |
| base64 图片 | 44 | 5.00 | 过 |
| commit 哈希 | 40 | 3.97 | 不过 |
| DB_PASSWORD | 7 | 2.81 | 不过 |
阈值 4.2 之下,base64 图片和 sk-live 密钥都在同一侧。 也就是说,熵这条规则分不清「密钥」和「图片片段」。 它唯一的价值是「找不像正常文字的串」,不能当密钥检测用。 真正拦住 hunter2 的不是任何规则,是「禁止明文口令进仓库」这条规矩。 规则管「长什么样的串」,规矩管「什么样的东西根本不该出现」。 两者缺一不可,但规矩在前面。
三、合规的底线
Demo 最后一段真跑了一次误提交:先把 .env 提交上去,再 git rm --cached 并提交一次。 然后分别在当前树和全部历史里搜那把密钥。 当前树里搜到零处,全部历史里搜到两处。 两处分别来自那次加文件、那次删索引——删索引这个动作本身,又把密钥在 diff 里写了一遍。 这条对照的含义很硬:git rm --cached 只是让它以后不再被跟踪,历史里的那次提交还在。 任何人 clone 之后翻 log 都能拿到。 真出这种事,动作只有两个,而且顺序不能反:立刻吊销那把密钥,然后用 filter-repo 重写历史。 只做第二条没用——密钥已经泄了,重写历史只是让后来的人看不见。 最后是一张能落地的检查表。
| 时机 | 查什么 | 靠什么 |
|---|---|---|
| 提交前 | 前缀密钥、私钥文件 | pre-commit 钩子 |
| 合并前 | 个人信息、内网地址 | CI 里的扫描 |
| 发给 Agent 前 | 有几个人名、几个地址 | 自己看一眼 |
| 每月一次 | 权限清单还有效吗 | 人工过一遍 |
第三行没有工具能替。它只需要三秒,但能挡掉大部分事故。 前两行能自动化,所以能交给流水线;第三行不能自动化,所以要变成一个习惯。
四、动手 Demo:授权、隐私与安全边界:九条里漏一条
九个文件埋十处敏感内容,跑五组扫描规则,逐条对账算召回和误报。 再验一次 .env 误提交之后,历史里到底还剩什么。 最后算五个串的熵,看高熵这条规则为什么靠不住。
RE_SECRET = re.compile("(?:sk-(?:live|proj)-|ghp_)[A-Za-z0-9_-]{16,}")
RE_TOKEN = re.compile("[A-Za-z0-9+/=_-]{20,}")
RE_PHONE = re.compile("1[3-9]\\d{9}")
RE_ID = re.compile("\\d{17}[\\dXx]")
RE_PATH = re.compile("(?:[A-Za-z]:[/\\\\]|/(?:Users|home)/)\\S+")
RE_IP = re.compile("\\b(?:10\\.|192\\.168\\.)\\d+\\.\\d+")
def ent(s):
c = {}
for ch in s:
c[ch] = c.get(ch, 0) + 1
n = float(len(s))
return -sum(v / n * math.log2(v / n) for v in c.values())
实跑输出:
--------------------------------------------------------------------
授权、隐私与安全边界:九条里漏一条
--------------------------------------------------------------------
这一节不讨论「要不要给 Agent 看代码」,讨论更具体的问题:什么样的数据一旦离开这台机器,
就算出事了。
① 数据出域的四个层次
--------------------------------------------------------------------
| 层次 | 边界 | 典型内容 |
|---|---|---|
| ① 本机 | 不出这台机器 | 调试日志、临时缓存 |
| ② 内网 | 不出公司网络 | 内网地址、真实表名 |
| ③ 供应商 | 发到模型厂商 | 粘进对话的代码和报错 |
| ④ 公网 | 谁都看得见 | 公开仓库、贴出去的 issue |
四层是一条单向的坡:往上走容易,往下收不回来。粘贴一段日志是第 ③ 层,发到 issue 里就是第 ④ 层。
! 判断某个东西能不能给 Agent 看,只需要问一句:这段话如果出现在公开 issue 里,
我会不会立刻去改密码。答案是「会」,它就至少是第 ③ 层。
② 造一个九文件的仓库,跑五组扫描
--------------------------------------------------------------------
九个文件里埋了 10 处敏感内容,从密钥到内网 IP 都有。
| 规则 | 命中 | 第一条长这样 |
|---|---|---|
| 前缀密钥 | 3 | sk-live-8f3a9c2b7d1e4f6a0b |
| 高熵串 | 1 | CzBVep/E6Q4zWH2ix+wRNluApc |
| 手机号 | 2 | 13812345678 |
| 身份证 | 1 | 11010519900307123X |
| 邮箱 | 1 | [email protected] |
| 绝对路径 | 1 | /Users/zhang/work/secret-project/c |
| 内网 IP | 2 | 10.12.33 |
五组规则一共报了 11 条。
③ 命中不等于都对:逐条对账
--------------------------------------------------------------------
11 条命中里,对上真实敏感项的有 9 条,另外 2 条是误报。
| 指标 | 值 | 怎么算的 |
|---|---|---|
| 召回 | 9/10 | 十处敏感项抓到几处 |
| 准确 | 9/11 | 报出来的有几条是真的 |
| 误报 | 2 | 图片片段一条,身份证里的号码一条 |
两个误报各有来头:高熵那条命中的是一段 base64 图片;手机号规则从身份证号里又抠出一个十一数字。
这说明规则之间会互相重叠,误报几乎是必然的。
所以扫描结果的正确用法是「人工确认清单」,不是「直接删除清单」。
④ 漏掉的那一条,恰好最常见
--------------------------------------------------------------------
.env 里有一行 DB_PASSWORD=hunter2。
前缀规则不认它(没有 sk- 之类的前缀),高熵规则也不认它(才七个字符,熵很低)。
算一下几个串的熵,就明白为什么高熵这条规则靠不住:
| 串 | 长度 | 熵 | 高熵规则 |
|---|---|:---:|:---:|
| sk-live 密钥 | 40 | 4.33 | 过 |
| ghp_ 令牌 | 40 | 4.14 | 不过 |
| base64 图片 | 44 | 5.00 | 过 |
| commit 哈希 | 40 | 3.97 | 不过 |
| DB_PASSWORD | 7 | 2.81 | 不过 |
阈值 4.2 之下:base64 图片和 sk-live 密钥都过线,ghp_ 令牌、commit 哈希、hunter2 都不过线。
也就是说,熵这条规则分不清「密钥」和「图片片段」——两者在同一侧。
它唯一的价值是「找不像正常文字的串」,不能当密钥检测用。
! 真正拦住 hunter2 的不是任何规则,是「禁止明文口令进仓库」这条规矩。
规则管长什么样的串,规矩管什么样的东西根本不该出现。
⑤ .env 提交上去了,删掉文件不等于删掉历史
--------------------------------------------------------------------
当前树里搜 sk-live:0 处(退出码 1,表示没搜到)。
全部历史里搜 sk-live:2 处。两处分别来自那次加文件、那次删索引。
git rm --cached 只是让它以后不再被跟踪,历史里的那次提交还在。任何人 clone 之后翻 log 都能拿到。
真出这种事,动作只有两个:立刻吊销那把密钥,然后用 filter-repo 重写历史。
两条都得做,只做第二条没用——密钥已经泄了。
⑥ 一条能落地的检查
--------------------------------------------------------------------
| 时机 | 查什么 | 靠什么 |
|---|---|---|
| 提交前 | 前缀密钥、私钥文件 | pre-commit 钩子 |
| 合并前 | PII、内网地址 | CI 里的扫描 |
| 发给 Agent 前 | 这段里有几个人名、几个地址 | 自己看一眼 |
| 每月一次 | 权限清单还有效吗 | 人工过一遍 |
第三行没有工具能替。它只需要三秒,但能挡掉大部分事故。
! 本书配套仓库里的示例密钥全是假的、不可用的形状。真按上面的形状造一把去试,那是另一件事。
五组规则报十一条、真九条、误报两条,这个成绩算好的。
两条误报一条来自高熵(base64 图片),一条来自手机号规则从身份证里又抠了个十一数字。
这说明扫描器不是「判官」,是「清单生成器」。
熵表五行值得逐行看。
ghp_ 令牌的熵只有四点一四,比 base64 图片低得多——所以「熵高就是密钥」这个直觉是错的。
而 sk-live 密钥熵四点三三,刚过线,靠的是前缀规则兜住。
hunter2 熵二点八一,两条规则都不认它。
但它恰恰是最常见的泄露形态:明文口令写在 .env 里,.env 被顺手提交。
规则管不了这个,只有规矩管得了。
第五段那个对照是全篇最该记住的数:当前树零处,全部历史两处。
零和两之间的差,就是「删掉文件」和「删掉历史」的差。
演示里的密钥全是我造的假串,形状真实但不可用。
这一点要说清:拿真密钥去试扫描器,那是另一回事。
30 秒实验
- 在你的仓库里 grep 一遍 sk- 和 ghp_ 开头、长度超过二十的串。 命中的每一条都当成真的,逐条确认。
- 检查你们的 .gitignore 里有没有 .env。 没有就加上,然后 git log --all -p | grep 一遍历史。
- 写下三条「绝对不该出现」的东西,比如明文口令、真实身份证、私钥文件。 这三条是规矩,不是规则,它们不进扫描器。
常见误区
- 「扫描器报了就是有问题,直接删。」 demo 里十一条有两条是误报。 直接删会删掉正经内容,之后没人再信这个扫描器。
- 「熵高的串就是密钥。」 base64 图片熵五点零零,比多数令牌都高。 熵只说明「字符杂」,不说明「这是密码」。
- 「文件删了,历史就干净了。」 demo 里当前树零处、历史两处。 删文件只是让它不再被跟踪,那次提交永远在。
自测三问(点开对答案)
1. 为什么「出域」按层次分,而不按敏感度分? —— **因为动作不同:本机不出门、内网不出网、供应商要脱敏、公网要吊销。** **敏感度只告诉你「多要紧」,层次告诉你「该做什么」。** 2. 召回九成、准确九成,这个扫描器能用吗? —— **能当清单用,不能当判官用。** **漏的那一成要人工补,误报的那两成要人工剔,两件事都得有人。** 3. 为什么「吊销密钥」要排在「重写历史」前面? —— **因为泄露已经发生,历史只是记录。** **先吊销能立刻止损,先重写只是让记录好看。**边界守住之后,剩下的是分工。哪些活该交给它,哪些必须人做,下一节给两个判据。
- ☐ (本篇完,下一篇 14.3 让 Agent 做噪声工作,让人做关键判断)