Scrutineer:在不淹没维护者的情况下扫描开源代码Scrutineer: scanning open source without flooding maintainers
开源代码安全审计中,发现漏洞其实是最简单的环节,真正的挑战在于如何合理地报告和修复它们而不给维护者带来过载。Scrutineer 工具致力于在扫描开源项目时,自动过滤无效或低危问题,避免向维护者发送海量无意义的安全警报。这种机制在保障软件供应链安全的同时,极大减轻了开源社区的维护负担。合理的警报分发策略是实现可持续开源安全治理的关键。
Andrew Nesbitt
Scrutineer 会扫描开源代码仓库以查找安全漏洞,并负责后续的所有工作:验证每一个漏洞、找出联系人、起草修复方案,并持续跟踪直到发布安全公告。过去几个月,我一直在为 Alpha-Omega 开发这个工具。
大语言模型让发现开源代码中的漏洞变得容易得多。将其指向一个代码库,它就能比以前的模糊测试工具和扫描器更快、成本更低地找出真实的 bug,尽管也会夹杂一些虚构的 bug,但瓶颈并未随之改变。每一条发现仍需由维护者去阅读、确认和修复,而他们的时间和注意力是整个生态系统赖以生存的有限资源。试图用机器生成的报告对维护者进行狂轰滥炸来保障一切安全,只会耗尽这些中坚力量。
今年五月,当我用几个 AI 扫描器扫描 curl 时,大部分输出结果都不符合该项目自身的披露政策,而真正有价值的发现却被淹没在其余的报告中。Scrutineer 的设计初衷就是确保模型生成的大量报告永远不会直接落到维护者手中。
你只需通过 URL 添加一个代码库,它就会针对代码运行一系列技能流水线,并在 Web UI 中呈现结果以供分类处理。它已经交到了生态系统安全工程师以及 Alpha-Omega 资助的几个团队手中,在我们的共同努力下,借助它已经发现、报告、修复并发布了相当数量的漏洞。
扫描的运行方式
每次扫描都是磁盘上的一个技能:包含一个 SKILL.md 文件、一个用于输出的 JSON schema,以及它所需的任何脚本。当你添加代码库时,分类(triage)技能会首先运行,并并行排入流水线中的其余部分。返回的结果是一组结构化的发现,每个发现都带有严重程度、CWE、链接回源代码行的确切位置、受影响的版本,以及说明如何触发该漏洞的六步追踪记录。
由于技能只是目录中的文件,因此更改运行内容只需编辑 Markdown,而无需重新编译扫描器。默认的技能集合位于 skills/ 目录中,分类技能的 SKILL.md 列出了要触发的技能,而放入一个新目录即可增加一种扫描类型,完全不需要修改代码。
技能
每个技能都是 GitHub 上 skills 文件夹中的一个目录。triage(分类)技能首先运行,收集供审计使用所需的上下文,而静态分析、去重和导出等辅助技能则在外围提供支持。真正塑造该工具运作方式的核心技能如下:
默认捆绑了三十多个技能,随着我们遇到现有技能无法覆盖的场景,这个列表还在持续增长。
底层数据
在阅读任何代码之前,scrutineer 获取的关于项目的大量信息都来自于 ecosyste.ms。元数据、包、公告和依赖项等技能都会查询它的 API:仓库元数据、每个已发布的包及其下载量和依赖数、已提交的已知公告,以及一旦出现漏洞会受影响的下游项目。维护者分析则依赖于来自同一来源的注册表所有权数据。依赖项信息是在本地读取而非远程获取的:依赖项和 sbom 技能在检出的代码上运行 git-pkgs,以索引代码树中的每个清单文件并生成 CycloneDX SBOM,从而使依赖图反映出仓库所声明的内容。这样一来,发现的漏洞就会附带上下文信息,比如该包的使用范围以及谁依赖它,而不是仅仅给出一个干瘪的行号。
披露前的验证
贯穿这些技能的核心原则是排除发现项(finding),而不是收集它们。深度分析只有在能够追踪到恶意输入时,才会保留一个汇聚点(sink)。breaking-change 和 patch 技能会返回未知或直接拒绝,而不是做出错误的判断;threat-model 会记录项目文档中声明的不属于问题的内容,这样它们一开始就不会被当作漏洞提出来。将举证责任加诸于发现项之上,可以将模型产生的大部分噪音留在工具内部,而不是进入某个人的收件箱。而且,如果项目在其 SECURITY.md 或威胁模型中记录了哪些情况不属于漏洞,这就为任何扫描器(不仅仅是这个工具)提供了从源头直接丢弃这些报告的依据。
scrutineer 之所以拥有一整套工作流而不是仅仅一个“报告”按钮,是因为模型直接生成的发现项并不适合直接发送给任何人。每个发现项都从新建状态开始,经过验证、分类、披露草稿和报告等环节,且每个步骤都设有人工审核关卡。对于高危和严重级别的发现项,在进行任何高成本工作之前,会首先自动将其加入一个低成本的只读分类器队列,将它们分类为真阳性、假阳性、已修复或不确定。如果严重级别的发现项被判定为真阳性,随后将触发一个独立的验证流程。在人工进行审查之前,任何信息都不会发送给维护者;而当信息最终送达时,它是通过 GitHub 的私密漏洞报告功能,作为一个已验证的漏洞并附带了建议的补丁,而不是另一个听起来似乎有理的“可能”,让维护者不得不牺牲周末时间去反驳它。
发现项的导入与导出
发现项不必源于 scrutineer 也能通过其工作流进行处理。向它 POST 另一个扫描器的输出或渗透测试报告,无论是 SARIF、CSV、markdown 还是最简形式的 JSON,每一项都会进入与原生发现项相同的分类和披露流程,并通过内容指纹与已有内容进行去重。上传的 CycloneDX 或 SPDX SBOM 会将每个组件解析到对应的源代码仓库,并将其排入扫描队列。输出的格式则是协调员或注册表能够接受的:发现项可以导出为 OSV 记录或 CSAF 2.0 公告,而披露包会将 OSV、CSAF、markdown 报告和补丁打包成一个 tarball,以备不通过 GitHub 私密报告渠道时使用。
在你的整个生态系统中尝试它
scrutineer 采用 MIT 许可证,代码托管在 GitHub 上。它在本地运行,扫描默认在一个临时的 Docker 容器中进行,该容器具有只读的源代码挂载和出站白名单。你可以通过 Claude Code 订阅令牌或 Anthropic API 密钥将其指向 Claude。
我现在最希望的是,大家能拿它去测试一下那些我不常接触的生态系统中的代码仓库。该流水线依赖于 ecosyste.ms 和各大包注册中心,因此已经覆盖了 npm、PyPI、RubyGems、crates.io、Go、Packagist、Hex 和 NuGet,但这些技能是基于我们碰巧最先扫描的项目而塑造的。如果你所在的生态系统有其专属的规范,且默认技能不够用,通常只需添加一个新的技能文件即可解决,这算是开源社区里要求的最小贡献了,欢迎在 issue 追踪器里向我反馈遇到了什么问题。
需要完整排版与评论请前往来源站点阅读。