🛠️ 第14章 Codex 的边界、风险与工程方法论
📄 一句话概括

数据出域是一条单向的坡:本机、内网、供应商、公网,往上走容易,往下收不回来。 扫描规则一定有漏有误:十处敏感项抓九处、报十一条里两条误报,这已经是好结果。 熵检测不出密钥,也拦不住弱口令;真正拦住 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 秒实验

  1. 在你的仓库里 grep 一遍 sk- 和 ghp_ 开头、长度超过二十的串。 命中的每一条都当成真的,逐条确认。
  2. 检查你们的 .gitignore 里有没有 .env。 没有就加上,然后 git log --all -p | grep 一遍历史。
  3. 写下三条「绝对不该出现」的东西,比如明文口令、真实身份证、私钥文件。 这三条是规矩,不是规则,它们不进扫描器。

常见误区

  1. 「扫描器报了就是有问题,直接删。」 demo 里十一条有两条是误报。 直接删会删掉正经内容,之后没人再信这个扫描器。
  2. 「熵高的串就是密钥。」 base64 图片熵五点零零,比多数令牌都高。 熵只说明「字符杂」,不说明「这是密码」。
  3. 「文件删了,历史就干净了。」 demo 里当前树零处、历史两处。 删文件只是让它不再被跟踪,那次提交永远在。
自测三问(点开对答案) 1. 为什么「出域」按层次分,而不按敏感度分? —— **因为动作不同:本机不出门、内网不出网、供应商要脱敏、公网要吊销。** **敏感度只告诉你「多要紧」,层次告诉你「该做什么」。** 2. 召回九成、准确九成,这个扫描器能用吗? —— **能当清单用,不能当判官用。** **漏的那一成要人工补,误报的那两成要人工剔,两件事都得有人。** 3. 为什么「吊销密钥」要排在「重写历史」前面? —— **因为泄露已经发生,历史只是记录。** **先吊销能立刻止损,先重写只是让记录好看。**

边界守住之后,剩下的是分工。哪些活该交给它,哪些必须人做,下一节给两个判据。

相关笔记