GitHub 的密钥检测模型:更少噪音,找到更多密钥
我们的新流程 vs. 之前的 LLM 流程:密钥扫描更智能,成本大幅降低
- 接受率提高 11.5%——按仓库所有者加权计算的告警精确度
- 中位扫描执行时间降低 86%——推送后扫描
- 服务成本降低 98%——紧凑模型 vs. 远程 LLM 调用
只有当开发者信任扫描结果时,密钥扫描才能发挥最大作用。漏报太多,真实凭证就可能一直留在仓库里;告警太频繁,开发者又会把时间花在排查那些并非密钥的字符串上。通用密钥尤其难平衡:同一个字符串既可能是真实凭证,也可能是文档里的占位符,要准确判断,就得理解它周围的代码和文本。
为了解决这个问题,我们训练了一个专门针对 GitHub 产品需求的密钥分类器:既能区分真实凭证和长得像的字符串,又能满足大规模扫描对速度和效率的要求。在配套文章《密钥保护必须跟上软件规模》中,我们解释了为什么密钥保护必须跟得上 AI 加速的开发节奏。本文则带你走进这个上下文感知分类器的幕后:我们如何设计、训练和评估它,做到用更少的噪音找到更多真实密钥。
我们实现了什么
GitHub 密钥扫描在两个关键时机发挥作用:
- 推送保护:在推送被接受之前扫描变更,帮助阻止密钥进入仓库,让开发者有机会及时处理。
- 推送后扫描:代码提交之后,持续扫描仓库内容,发现潜在密钥时生成告警,供开发者和安全团队排查并修复。
我们开发了一个快速分类器,把中位推送后扫描执行时间比之前基于 LLM 的流程减少了 86%。有了这样的效率,通用密钥检测也能用在推送保护上了——在开发者推送变更之前就返回结果,而不是等代码提交之后才扫描。
为什么密钥难检测
结构化凭据通常带有独特的固定前缀、长度、字符集或校验和,通用型密钥则未必。它们的写法与构建 ID、校验值、资源哈希、示例值以及其他非敏感字符串高度相似,很容易混淆。
真正决定判断的,是上下文。
| 很可能是真实凭据 | 很可能是无害的相似串 |
|---|---|
| 紧挨着生产数据库地址写的密码 | 测试夹具(fixture)里形状相同的密码 |
| 部署配置中的连接字符串 | 安装指南里指向 localhost 的连接串 |
| 与某个 API 凭据关联的高熵值 | 校验和、构建 ID 或资源哈希形式的高熵值 |
这类字符串属于「难负样本」:表面上符合凭据的匹配规则,实际上并不是可用凭据。靠增加规则可以压掉其中一部分,但每加一条例外,都可能顺带漏掉真正的密钥。改用训练好的分类模型,就可以借助文件路径、密钥类型、变量名、周边代码或文字等信号来判断。
正因如此,我们上线了一个新的扫描器,采用两阶段流水线:
- 候选生成:用正则表达式对内容做高召回率的初筛,尽快找出各类疑似凭据的字符串。这一阶段追求的是快速、确定性地捕获大范围的候选。
- 上下文感知分类:用微调过的 ModernBERT 分类器逐个评估候选及其上下文。它学会识别区分真实密钥与误报的信号,包括占位值、示例、文档片段、生成内容,以及非敏感标识符。
模型体积小,在我们的推理基础设施上跑起来很高效,因此服务成本低得多,扩容也比基于 LLM(大语言模型)的流水线简单。
效果
- 用户接受率提升 11.5%,误报减少。
- 推送后的密钥检测执行时间缩短 86%。
- 与基于 LLM 的流水线相比,成本节省超过 98%。
检测管道的工作原理
扫描内容时,正则表达式先撒下一张大网,凡是看起来像凭据的字符串都会被标记出来,确保一开始不会漏掉真正的密钥。每个候选字符串随后会交给一个经过微调的 ModernBERT 分类器,由它结合文件路径、变量名和上下文代码一起判断。分类器会估算这个候选值是真密钥的概率。置信度高的会生成告警;而占位符、测试夹具、校验和这类“长得像”的内容,会在到达开发者之前就被过滤掉。
先广撒网,再仔细甄别
第 1 步 — 扫描
覆盖仓库内容、提交、拉取请求以及其他需要扫描的入口。
第 2 步 — 生成候选
用正则表达式找出可能的令牌、密钥和类似凭据的字符串。
第 3 步 — 分类
微调后的 ModernBERT 结合候选值和上下文,判断它有多大概率是真密钥。
第 4 步 — 告警或抑制
置信度高的继续往下走,大概率是误报的直接过滤掉。
分类器在其中扮演什么角色
关键的设计选择在于:模型并不取代确定性扫描,而是对它进行补充。正则表达式仍然是快速找出候选字符串的合适工具。机器学习真正能发挥作用的地方,是把“看起来像密钥”和“大概率是真密钥”区分开,并把每个候选值分流到告警或抑制。分类器就用在这里。
困难负样本:几乎以假乱真的“密钥”
大多数误报并不是随机产生的。它们属于困难负样本:能匹配上凭据模式,但实际上并不是可用凭据的字符串。比如文档里的占位符、测试中的玩具示例、脱敏后的值、非敏感 ID,以及生成的测试数据。这些内容用正则很容易匹配到,但纯规则系统想抑制它们,又很容易误伤真正的密钥。
ModernBERT 的优势在于,它能把候选密钥和附近的代码、文本一起读进去,做出类似大语言模型(LLM)那种上下文层面的理解,却不用像生成自然语言那样付出延迟和算力开销。一个候选密钥如果周围全是 “example”“dummy”“fake” 或 “replace-me” 这类词,风险通常比部署文件里赋给环境变量的候选密钥低。分类器从标注样本中学习这些上下文差异,不必让工程师把每一种例外都写成规则。
为什么选 ModernBERT
选 ModernBERT,是因为它很适合这类工作:体积小、效率高,擅长对代码周边文本做分类。密钥扫描要在很高的吞吐、很低的延迟下,对较短的候选窗口给出校准过的判断,所以在流水线的这个阶段,微调过的编码器分类器比通用大语言模型更适合部署。模型可以直接在我们的判定边界上训练,并用对密钥扫描质量真正重要的召回率和精确率指标来评估。
我们怎么训练
难点在于,有些情况光看语法会产生误导,得找到有代表性的数据。训练和评估策略合并了三类来源:
- 产品侧样本:来自 GitHub 仓库,结合密钥检测功能的用户反馈,再加上人工验证。
- 高召回正则候选:来自 GitHub 仓库数据,包括人工标注样本和用来扩大覆盖范围的弱标签。
- 合成样本:针对稀疏、困难的场景,比如文档里的密钥、测试夹具、公钥、校验和、结构相似的仿冒项和近似样本对。合成数据要经过确定性校验和基于模型的质量审查,才能进入训练集。
每个模型输入包含候选值、文件路径、密钥类型和有限范围内的上下文。输出是一个分类分数,扫描器据此判断候选密钥是否继续往下走。
划分数据前,我们先把属于同一仓库、同一候选值或同一示例的行归为一组,避免近似重复的样本同时出现在训练集和评估集中。训练重点关注对告警质量最关键的情况:真实密钥、逼真的误报,以及正则表达式经常误匹配的困难负样本。
最重要的训练原则,是让模型看到容易混淆的样本。我们加入了与真实密钥句法相似但语义不同的候选,这样模型不仅学会密钥长什么样,还能学会在上下文中误报长什么样。
阈值通过离线评估选定,然后在类似生产环境的流量上验证,再扩大范围上线。训练好的模型导出为 ONNX 格式并做 8 位量化,从而实现快速推理。
评估结果
我们分两个阶段评估新流程在生产环境中的效果:先在同一批公开流量上与生产基线对比,再通过客户 A/B 实验验证。
- 离线评估: 在推送后扫描中,新流程在同一批公开流量上的精确率提高 13%,召回率提高 19%,优于之前的 LLM 流程。
- 在线评估: 我们通过针对之前 LLM 流程的 A/B 实验,验证这些质量提升是否转化为客户可感知的结果。
| 在线结果 | 相比之前 LLM 流程的变化 | 衡量内容 |
|---|---|---|
| 按仓库所有者加权的接受率 | 提高 11.5% | 客户对检出结果的接受程度 |
| 推送后扫描执行时间中位数 | 降低 86% | 生产扫描执行时间 |
| 服务成本 | 降低超过 98% | 紧凑分类器与远程 LLM 调用相比的成本 |
按仓库所有者加权的接受率提高了 11.5%。我们按仓库所有者对接受率加权,以减少那些一次性批量处理大量告警的客户带来的影响,否则简单平均值会被扭曲。
核心要点
最好的安全工具,是开发者愿意用的那一个。精确率更高,干扰性告警就更少;召回率更高,就能抓到更多真正的密钥;成本更低,我们就能持续扩大覆盖范围,而不至于让扫描贵到用不起。对开发者和安全团队来说,这应该意味着更少的误报,以及在密钥最可能出现的地方——提交(commit)、拉取请求(PR)和日常开发流程——获得更快的反馈。
混合方案很强。正则带来速度、透明度和广泛的候选覆盖;ModernBERT 则带来对上下文的学习理解,帮助减少误报。在这个产品约束下,两者结合的效果胜过任何单独一种。我们还明白了一点:模型大不等于模型好用。当任务足够聚焦、决策边界可以量化、推理效率又很关键时,一个更小的微调分类器反而才是正确答案。
这只是让密钥扫描更准确、更易扩展、更好用的其中一步。
未来展望
我们聚焦两个方向:
- 打造持续改进的飞轮。 我们会利用生产环境的反馈闭环,找出新的漏报和误报,把真实世界的信号转化为质量更高的训练与评估数据。
- 探索新的模型架构。 我们正在研究能在一步内完成密钥检测和提取的方法,从而简化流水线,同时保持准确性和规模。