news 2026/10/28 4:16:31

软件供应链安全落地:SBOM、SCA与SLSA在CI/CD中的协同实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
软件供应链安全落地:SBOM、SCA与SLSA在CI/CD中的协同实践

软件供应链安全这件事,2026 年已经不再是"要不要做"的问题,而是"做的这些东西到底有没有用"的问题。SBOM、SCA、SLSA 这三个词在过去两年被反复提及,几乎每一份安全建设规划里都会出现,但我见过太多团队的真实状态是:流水线里加了一个扫描步骤,产出了一堆 JSON 文件堆在某个存储桶里,没人看,也没人知道该怎么用;构建平台号称支持生成 provenance,但从来没人在部署前验证过它;SCA 一周扫出四千条高危,安全团队发邮件,研发团队加白名单,最后大家一起把这个环节当成噪音源关掉。这不是工具的问题,是从一开始就没搞清楚这三个东西各自解决什么问题、边界在哪里、在流水线的哪个位置该扮演什么角色。下面我按"概念澄清—单项落地—串联成线—踩坑复盘"的顺序,把 SBOM(软件物料清单)、SCA(软件成分分析)、SLSA(软件制品供应链等级)这三件事从原理到实操讲透,重点放在怎么让它们真正在 CI/CD 里跑起来并且持续产生价值,而不是又变成一份应付检查的文档。适合正在做供应链安全落地、被扫描告警淹没、或者准备给构建流水线加卡点的同学参考,基础薄弱的也能看懂,因为我会把每个概念先翻译成人话。

1. 三个词经常被并列提起,但它们回答的根本不是同一个问题

1.1 先把"供应链安全"拆成三个可回答的问题

我在内部做分享时,从来不从定义讲起,而是先问三个问题:第一,我这个制品里到底装了什么?第二,这些东西里有没有已知的坏东西?第三,我怎么证明这个制品确实是我用我以为的那份代码、在那台我以为的机器上构建出来的?

这三个问题分别对应 SBOM、SCA、SLSA。它们之间有依赖关系,但绝对不是替代关系。很多人以为上了 SCA 就等于有了 SBOM,或者以为有了 SBOM 就自动获得了供应链安全能力,这是最常见的认知错位。SCA 是"检查动作",SBOM 是"检查的依据和产物",SLSA 是"检查结论的可信度担保"。

换个角度理解:如果没有一份准确、完整、机器可读的组件清单,SCA 扫描出来的结果就只是基于它自己那套解析逻辑的推测,你不知道它漏了什么;而如果这份清单本身可以被随意篡改,或者你根本不知道它是从哪个版本、哪次构建产生的,那么基于它做出的任何安全判断都建立在流沙上。这就是为什么这三个东西必须一起谈,单独拎出任何一个都会形成一个看似完整实则漏风的闭环。

顺带说一句,这三个概念的成熟度差异很大。SBOM 有明确的格式标准,落地门槛最低;SCA 是商业化最成熟的一块,工具最多但效果参差;SLSA 是最"重"的,涉及构建基础设施改造,推进速度最慢,但一旦建成,对整条链路的可信度提升也最明显。团队排优先级时不能按"哪个词更火"来,要按"我当前最缺哪一环"来。

1.2 SBOM 的真实定位:一份会过期的成分表

SBOM 这个词被翻译成"软件物料清单",我觉得"物料清单"这个译法反而容易让人误解成一份静态的资产台账。它更像食品包装背面的成分表——但它有一个致命差异:食品的成分表在保质期内是稳定的,软件的成分表可能在你构建完成的第二天就过时了,因为某个你依赖的传递依赖被披露了新漏洞。

所以 SBOM 的正确心智模型是"某一次构建产物的快照",而不是"我们公司用了哪些开源软件的清单"。这两者的差别决定了你的落地方式:快照必须跟着产物走,产物在哪儿 SBOM 就在哪儿;而台账是集中维护的,一旦集中维护就必然和实际产物脱节。

我在评审过的一些实现里见过一个典型错误:有人建了一个独立的仓库,把所有项目的 SBOM 都提交进去,每天定时更新。这个做法看起来整齐,实际上把 SBOM 和产物之间的绑定关系彻底切断了——你没法回答"线上正在跑的这个镜像,它的 SBOM 是哪一份"。正确的做法是 SBOM 作为构建产物的一部分,和二进制、镜像一起进入制品库,通过镜像 digest 或者产物哈希建立索引关系。

1.3 SCA 的真实定位:拿成分表去对风险库

SCA 做的事情本质上是一次"连接查询":左边是组件清单(理想情况下来自 SBOM,实际上很多工具是自己现场解析出来的),右边是漏洞库、许可证库、维护状态库,中间靠包标识符匹配。它的输出不是"你的软件有漏洞",而是"你依赖的某个组件版本,在某个漏洞库的记录里被标记为受影响"。

这个区别非常重要,因为它直接解释了为什么 SCA 的误报率会那么高。漏洞库的记录是基于版本区间和包标识做的,它不知道你到底调用了这个组件里的哪个函数,也不知道你用的是不是受影响的那个编译选项。所以 SCA 的结果本质上是一个"嫌疑人名单",不是"定罪书"。把嫌疑人名单当定罪书用,就会得到"阻断一切"的荒谬结论,最终逼迫团队绕过门禁。

我个人的经验是:SCA 的价值 80% 取决于你怎么处理它的输出,20% 才取决于工具本身的检出能力。市面上主流的几款工具在核心匹配能力上差距没有宣传的那么大,真正拉开差距的是去重、优先级排序、修复建议的可用性,以及和工单系统的集成顺畅度。

1.4 SLSA 的真实定位:证明这张成分表没被掉包

SLSA 解决的是一个更底层的问题:你怎么相信前面那份 SBOM 和那次 SCA 的结果是真的?如果你的构建环境里有人能改依赖、能改构建脚本、能往产物里塞东西,那么 SBOM 上写的和产物里实际有的可能完全是两回事。

SLSA 通过"构建溯源"(provenance,也就是一份描述"这个产物是怎么来的"的签名声明)来回答这个问题。它记录的是:用了哪个仓库的哪个 commit、构建在哪个平台、用什么构建配置、输入是什么、输出是什么。这份声明由构建平台签名,下游可以验证签名并检查内容是否符合预期。

这里要特别强调一点,也是我在很多场合反复纠正的:SLSA 是"构建过程的等级",不是"软件的安全等级"。一个通过 SLSA L3 的产物,可能是功能上漏洞百出的,也可能是代码质量很差的;它只保证"这个产物确实是由这份源码在这个构建平台上产出的,中间没有被人为篡改"。把 SLSA 当成安全认证去宣传,是典型的误用。

1.5 用一张表把三者的边界钉死

维度SBOMSCASLSA
回答的问题里面有什么这些东西有没有已知风险这个制品是否按声明的方式构建
产出物组件清单文件(JSON/XML)漏洞与许可证报告签名后的溯源声明
时效性单次构建快照依赖漏洞库实时变化单次构建快照,永久有效
主要消费者下游用户、应急响应、审计研发团队、安全团队部署方、制品消费方
落地难度低,半天能跑通中,难在降噪和流程高,涉及构建平台改造
失败模式生成了但没人保存、没人关联告警过多导致整体被忽略签了但没人验,等于没签

这张表我建议打印出来贴在工位上。每次有人问"我们是不是做了 SBOM 就不用做 SCA 了",指着第三行看就行——SBOM 是静态快照,SCA 是对着动态漏洞库做的比对,两者的时间维度完全不同,缺一个都不成立。

2. SBOM 落地:格式选型、生成时机和那几个必填字段

2.1 SPDX 和 CycloneDX 该怎么选

目前主流就这两个格式,SPDX 出身于许可证合规场景,历史更久,已经被纳入国际标准体系;CycloneDX 出身于安全场景,结构更紧凑,对漏洞声明、服务依赖、运行时信息的表达能力更强。两者的信息模型有重叠,也各有对方没有的字段。

我的选型建议非常直接:如果你们的主要诉求是给客户交付、应付合规审计、做许可证合规,选 SPDX,因为它的受众认知度更高,法务和安全审计方更容易接受;如果主要诉求是内部安全运营、要和 SCA 结果联动、要做 VEX 声明,选 CycloneDX,因为它原生就有漏洞和 VEX 相关的结构,联动起来少走一层转换。

但现实情况往往是两边都要。这时候不要试图自己写转换器去双向无损转换——我试过,字段语义的差异会导致大量信息丢失,尤其是依赖关系图和许可证表达式的复杂情况。比较务实的做法是选定一个作为"主格式"用于内部流转和存储,另一个作为"交付格式"在需要时按需生成,并且明确接受交付格式是有损的。

还有一个经常被忽略的点:格式版本。两个标准都在持续演进,不同版本的 schema 差异不小,字段增减、枚举值变化都会影响下游解析。落地时必须在文档里明确锁定版本号,并且在生成工具升级时做回归测试,否则某天工具默认版本一变,下游的解析器直接报错,排查起来能耗掉一整天。

2.2 生成时机的分歧:源码期生成还是构建后生成

这个分歧在实际项目里吵得最凶。源码期生成指的是在代码仓库层面跑扫描,基于锁文件、包管理器的元数据来推断依赖树;构建后生成指的是在构建完成的产物(镜像文件系统、二进制、打包产物)上做实际探测。

源码期生成的优点是快、早、能在 PR 阶段就给出反馈,缺点是它推断的是"应该装了哪些东西",而不是"实际装了哪些东西"。构建过程中如果有动态安装、有构建脚本临时拉取、有多阶段构建的产物层合并,源码期的推断就会漏。构建后生成的优点是准确、反映真实情况,缺点是慢,而且发现问题时已经构建完了,反馈链路长。

我的做法是两个都要,但用途严格区分:PR 阶段跑源码期的轻量扫描,目的是快速拦住明显的高危新增依赖,这是"防增量";构建完成后跑基于产物的完整生成,产出的 SBOM 才是随制品归档、对外交付的那一份,这是"保准确"。这两份 SBOM 不要混用,也不要用源码期的那份去替代产物那份,否则应急响应时你会对着错误的组件清单做判断。

另外提醒一个实操细节:多阶段构建的镜像,中间阶段装的编译工具链如果不清理,会被完整扫进 SBOM 里,导致清单里出现一堆运行时根本不存在的组件,既增加体积也制造噪音。要么在最终阶段做彻底清理,要么在生成工具里配置排除路径和阶段过滤。

2.3 一份"能用"的 SBOM 至少要有哪些字段

我见过很多 SBOM 只有组件名和版本号,这种清单在应急响应时几乎没法用。一份能真正支撑安全运营的 SBOM,我要求至少包含以下几类信息:

  • 包的唯一标识符。这是最关键的字段,没有它就无法和漏洞库做精确匹配。通用包 URL 这种标识方式比单纯的名字加版本可靠得多,因为它包含了生态类型、命名空间、名称、版本,能避免跨生态的同名包混淆。
  • 内容的哈希值。用于校验完整性,也是判断"这个组件是不是被替换过"的依据。
  • 供应方信息。出问题时你得知道该找谁,虽然很多开源组件的供应方字段是空的,但至少要把能从包元数据里拿到的都填上。
  • 依赖关系图。只列平铺的组件清单价值有限,有了依赖关系才能回答"这个漏洞是通过哪条路径进来的",也才能做后续的可达性分析。
  • 许可证信息。这块经常被安全团队忽略,但对交付型业务来说是硬需求。
  • 生成元信息。谁生成的、什么时间、用什么工具、什么版本、对应的产物哈希是什么。缺了这块,这份 SBOM 就无法自证来源。

覆盖率方面,我一般会用一个简单指标来衡量:抽取若干个已知的、明确在依赖树里的组件,检查 SBOM 里是否存在。如果连这些都找不到,说明生成工具的生态支持不全,需要补充其他工具做兜底,而不是假装清单是完整的。

2.4 让 SBOM 保鲜:与制品 digest 绑定、随产物归档

这一节是我认为整个 SBOM 落地里最重要、也最容易被跳过的一环。清单生成了,然后呢?

正确的链路是这样的:构建完成 → 计算产物哈希 → 生成 SBOM 并把产物哈希写进 SBOM 的元信息 → SBOM 随产物一起推送到制品库,用产物摘要作为关联键 → 下游拉取产物时可以按摘要反查对应的 SBOM。

这样一来,任何时候你拿到一个线上运行的镜像,都能通过它的摘要立刻定位到当初构建时生成的那份清单。发生新漏洞披露时,你的应急响应流程就变成了一次索引查询:拿漏洞影响的组件标识去查哪些产物摘要关联的 SBOM 里包含它,而不是让每个团队回去翻自己的依赖文件。

还有一个动作必须有:SBOM 的留存策略要跟着产物的生命周期走。产物在线上跑三年,SBOM 就得留三年;产物下线了,SBOM 可以按合规要求再留一段时间后清理。我见过有团队把 SBOM 存在 CI 的临时工作目录里,构建完容器一销毁,清单跟着没了,等到半年后要做漏洞回溯,发现什么都没有。

至于"保鲜"的另一层含义——组件本身在变化,但 SBOM 是快照不会自动更新——这个不是靠改 SBOM 解决的,而是靠 SCA 持续对着漏洞库重新比对。SBOM 不变,漏洞库在变,重新比对产生新的结论,这才是正确的工作模式。

3. SCA 落地:怎么从几千条告警里捞出真正要修的那几十条

3.1 依赖解析这一关没过,后面全是白干

SCA 工具的能力上限,首先被依赖解析的准确性卡死。解析不准,后面的匹配再精确也是错的。我在实际项目里遇到过的问题,基本集中在以下几类:

第一类是锁文件缺失或不一致。很多项目仓库里提交了锁文件,但 CI 构建时用的是另一套解析逻辑,导致构建实际用的版本和锁文件记录的不一致。这种情况下 SCA 扫的是锁文件,实际跑的是别的版本,结论自然对不上。解决办法很土但有效:在 CI 里加一步校验,确认构建时解析出的依赖树和锁文件一致,不一致就失败。

第二类是私有源和镜像源。企业内部往往有自己的包代理,同一个包从不同源拉取,元数据可能不同,SCA 工具如果配置里没加这些源,解析会失败或者解析出错误的依赖。

第三类是 vendored 代码,也就是把第三方代码直接复制进仓库的做法。这种情况依赖管理器完全看不到,SCA 默认也不会扫,但代码确实在产物里。需要单独配置目录扫描,并且手动维护版本信息。

第四类是开发依赖和生产依赖的区分。测试框架、构建工具这些只在开发期用的依赖,扫出来的漏洞优先级应该低很多,但如果工具不做区分,它们会和运行时依赖混在一起,直接把告警量翻倍。

我在做工具评估时有个必测项:准备一个包含多语言、有 vendored 目录、有私有源依赖的样例项目,看工具能不能把依赖树完整正确地还原出来。这一关过不了的,后面讲的所有降噪能力都是空中楼阁。

3.2 漏洞匹配为什么会误报:CPE、PURL 与版本区间

理解了匹配原理,就能理解误报从哪来。早期的 SCA 主要靠 CPE(通用平台枚举)做匹配,这个标识体系的粒度很粗,同一个组件在不同发行版、不同打包方式下可能对应同一个 CPE,导致把不相关的漏洞关联上来。现在更主流的是用包 URL 这类精确标识,配合版本区间判断。

但即便是精确标识,误报依然存在,主要来自三个地方:

一是版本区间的语义问题。不同生态的版本号规则不一样,语义化版本、日期版本、带后缀的发行版版本,比较逻辑各有各的坑。有些工具对预发布版本、修订号的处理不够严谨,会把不受影响的版本判成受影响。

二是同名前缀问题。有些项目会把不同产品线放在同一个命名空间下,版本号混排,导致版本区间判断出错。

三是漏洞库本身的滞后和错误。漏洞库的记录依赖人工和自动化流程维护,错误关联、版本区间写错的情况并不罕见。所以我一直强调,SCA 结果必须保留"人工判定"这一环,不能全自动阻断。

实操上我建议的做法是:把工具报出的每一条高危,都要求确认"受影响的具体函数或调用路径"。确认不了的,先标记为待定,通过 VEX 声明记录下来,而不是直接当成必须修复的缺陷。这个动作看起来很繁琐,但它能把告警量压到一个团队愿意认真处理的数量级。

3.3 三步降噪:可达性、VEX、优先级打分

降噪是 SCA 落地的核心工作,我一般按三步走。

第一步是可达性分析。核心问题是:这个有漏洞的组件,它的受影响代码到底有没有被你的程序实际调用到。Java 生态可以通过字节码调用图分析,JavaScript 生态可以通过模块引用图分析,其他生态的支持程度参差不齐。可达性分析的结论不是绝对的,它会有假阴性和假阳性,但把"不可达"的那部分降级,能把告警量砍掉一大半。需要注意的是,动态调用、反射、配置驱动的加载这些场景,静态分析看不出来,所以"不可达"的结论要留有余地,不能作为完全不处理的理由。

第二步是 VEX 声明。VEX 是一份机器可读的声明,用来表达"这个组件确实有漏洞,但对我的产品不构成影响,理由是某某"。常见的理由包括:不受影响的代码路径、漏洞所依赖的编译选项未启用、漏洞所依赖的功能未开启、以及有外部缓解措施。把人工判定结果沉淀成 VEX,最大的好处是可以自动化地抑制后续重复告警,避免每次扫描都要重新人工确认一遍。这是我认为投入产出比最高的一个动作。

第三步是优先级打分。不要只看 CVSS 基础分,那个分数假设的是最坏情况。实际排序我会综合几个因素:漏洞是否已有公开利用代码、是否有修复版本可用、组件是否在运行时依赖里、是否暴露在外部可访问的路径上。把这几个维度做成一个简单的加权打分,输出一个"本周该修的前二十条",比给团队一个四千条的列表有用一百倍。

降噪手段主要作用局限性建议落地顺序
可达性分析砍掉未调用代码路径的告警动态调用场景不适用第二
VEX 声明沉淀人工判定,抑制重复告警需要人工投入维护第三
优先级打分输出可执行的修复排序依赖准确的运行时信息第一
依赖范围过滤排除开发依赖噪音需准确识别依赖类型第一

3.4 门禁策略:哪些必须阻断,哪些只记录

门禁设计是个组织问题,不是技术问题。我见过最失败的模式是"全量阻断":只要有高危就卡住合并。结果是一周之内,所有人都在找绕过的方法,最后门禁形同虚设。

我推荐的策略是分层:

  • 必须阻断:新增依赖引入了已知有公开利用代码的高危漏洞,且有可用修复版本;或者引入的组件许可证与公司政策冲突。这两个条件都满足时才阻断,因为这是"改一下版本号就能解决"的事,成本低收益高。
  • 告警并要求限期处理:存量依赖中的高危漏洞,不可达性未确认的,进工单系统,给一个处理窗口。不要阻断,但要可见、可统计。
  • 只记录不告警:开发依赖中的漏洞、已通过 VEX 声明排除的、无修复版本的。这些进报表,不进工单。
  • 自动放行:低危、无利用条件、不在运行时路径上的。

关键原则是:阻断条件必须满足"可执行、可修复、成本可控"三条。做不到这三条的阻断,最终一定会被绕过。另外强烈建议给门禁加一个"临时例外"通道,带审批、带过期时间、带记录,让团队有正规途径处理特殊情况,而不是逼他们去改流水线配置。

4. SLSA 落地:从"我信任这个构建机"到"我能验证这个产物"

4.1 SLSA 各级到底在约束什么

SLSA 的构建等级,我习惯用一句话概括每一级在解决什么:

L1 是"有记录"。构建过程有自动化脚本描述,产出了溯源声明,但这份声明没有任何防篡改能力,谁都能改。它的价值是让你开始有意识地记录构建输入,是后面所有工作的基础。

L2 是"有签名,且构建在托管环境里"。溯源声明由构建平台签名,声明里包含源码仓库、commit、构建配置等信息。这一级拦住了最常见的一类攻击:在构建完成后替换产物,或者在构建过程中插入未经记录的步骤。因为签名由平台持有,攻击者要在不破坏签名的前提下改产物,难度陡然上升。

L3 是"构建环境加固,溯源不可伪造"。这一级的关键在于构建平台的设计:不同构建任务之间强隔离,构建过程的参数由平台在运行时确定而不允许构建脚本自行修改,签名密钥对构建过程不可见。到这一级,即使攻击者能控制你的源代码仓库,他也很难伪造出一份"看起来正常"的溯源声明。

再往上的等级(双人评审、封闭构建等)对绝大多数团队来说短期内不现实,我不建议一开始就奔着最高级去。我的建议是:先用三个月把 L2 做扎实,这是投入产出比最高的一档;L3 作为一到两年的目标,因为它涉及构建平台的选型和改造,属于基础设施投资。

4.2 一个团队从零到 L3 的现实路径

我参与过的一个项目,从完全没有溯源到稳定运行 L3,花了大约十个月。路径大致是这样的,我觉得有参考价值:

第一阶段(第一个月),盘点现状。梳理清楚有多少个构建入口、分别跑在哪些环境上、哪些是手工触发的、哪些构建产物会对外发布。这个盘点结果往往会让人吃惊——很多团队以为自己只有一条流水线,实际上一查发现有七八个不同的构建方式,包括某个人本地打的包直接上传的。

第二阶段(第二到四个月),把所有对外发布的构建统一到托管构建平台上,禁用本地构建上传。这一步是组织阻力最大的,因为总有人有"紧急情况需要本地构建"的理由。我的做法是保留一个例外通道,走审批、留记录、例外产物打特殊标记并且不能进入正式发布渠道。

第三阶段(第五到七个月),打开平台自动生成溯源声明,并且在发布流程里加验证步骤。这一步的坑在于:很多团队生成了声明但没有任何地方消费它,等于白做。必须在部署环节加一道校验,不通过就不允许部署。

第四阶段(第八到十个月),推动构建平台升级到支持更强隔离的形态,同时清理构建脚本里那些会动态修改依赖、动态下载资源的部分。这些是通往 L3 的主要障碍。

整个路径里,技术工作量大概占四成,剩下六成是推动组织统一流程、说服各方放弃绕过手段。这个比例要有心理准备。

4.3 溯源声明的生成与验证:两头都得做

生成这一端相对简单,主流的托管构建平台基本都能自动生成,你需要做的是确认几件事:声明里包含的源码信息是否指向正确仓库和 commit、构建参数是否被完整记录、声明是否被正确签名、声明的存储位置是否和产物绑定。

验证这一端才是真正的难点,也是绝大多数团队的缺口。验证要做的事情是:在部署或使用制品之前,检查随附的溯源声明,确认签名有效、签发者身份符合预期、源码仓库和分支符合预期、构建平台符合预期、声明中的产物哈希和实际部署的产物哈希一致。任何一项不满足,拒绝使用。

我特别要强调最后那个哈希比对,因为这是最容易被跳过的一步。声明是签名了的,产物也是从同一个地方下来的,看起来天然对应——但中间有太多环节可能出错,比如缓存污染、比如同名不同 tag 的镜像被覆盖。必须显式做一次哈希比对,这是整条链路上唯一能真正证明"声明的就是这个产物"的动作。

验证策略要写成代码,放进部署流水线里,而不是写成文档让人去检查。人对文档的执行率,在赶进度的时候是零。

4.4 自签自证为什么等于没签

这是我在评审中最常发现的问题:流水线里自己生成一份声明,自己用存在 CI 变量里的密钥签名,部署时用同一把密钥验证。这一套流程走下来,形式上完整了,但安全性提升是零——因为任何能修改流水线配置的人,都能同时修改生成逻辑和验证逻辑,或者直接换掉密钥。

要让签名有意义,必须满足"签名主体和验证主体分离"的原则。具体来说:签名密钥不能对构建脚本可见,由构建平台在受控环境中完成签名;验证方要独立于生成方,验证的是"这个声明确实由那个受信任的构建平台签发",而不是"这个声明确实由我们自己的流水线签发"。

如果做不到平台级托管签名,退而求其次的做法是用短期凭据:通过工作负载身份换取一次性的签名证书,证书有效期只有几十分钟,绑定具体的构建任务,用完即失效,并且把签发记录独立存储。这种做法比长期密钥好得多,因为长期密钥一旦泄露,历史所有声明的可信度全部归零。

5. 串进流水线:卡点位置、策略代码和一个能跑通的骨架

5.1 五个卡点,以及每个卡点该拦什么

把三件事放进 CI/CD,我一般划分成五个卡点,每个卡点只做一件事,不要混。

卡点一:提交与合并请求阶段。运行轻量级的依赖扫描,只检查新增或变更的依赖,不扫全量。拦什么:新增的高危且有修复版本的依赖、许可证冲突的新增依赖。这一关必须快,超过三分钟就会被抱怨。

卡点二:构建阶段。生成完整的 SBOM 和溯源声明。这一关不拦截,只产出。但要保证产出的东西被正确保存和关联。这里失败通常是因为工具报错被忽略,所以要把"SBOM 生成成功"作为构建成功的前置条件,生成失败就构建失败。

卡点三:制品入库阶段。对产物签名,把签名、SBOM、溯源声明一起推送到制品库,并建立索引。拦什么:签名失败、SBOM 缺失、溯源声明缺失的制品不允许入库。

卡点四:部署阶段。验证溯源声明、比对产物哈希、检查 SBOM 中是否存在被策略禁止的组件。拦什么:声明验证不通过、产物哈希不匹配、存在明确禁止的组件。

卡点五:运行时阶段。持续对已部署产物的 SBOM 做重新比对,新披露的漏洞进来时触发告警。这一关不阻断,只做发现和联动工单。

这五个卡点的顺序很重要,从早到晚,越早发现成本越低。但也不要试图把所有检查都塞到最早的那一关,那会让开发者体验崩掉。我的分配原则是:能在三分钟内做完的放最早,需要完整构建产物的放中间,需要外部信息的放最后。

5.2 一份流水线骨架

各家流水线平台的语法差异不小,下面这份是结构化示意,把关键的步骤和判断条件标出来,实际迁移时按平台语法调整即可。

stages: - name: precheck steps: - name: dependency-diff-scan # 仅扫描本次变更引入的依赖 run: sca-scan --diff --base main --block-on high --require-fix-available - name: license-gate run: license-check --policy allowlist.yaml --fail-on deny - name: build steps: - name: compile run: build-script - name: generate-sbom # 基于构建产物而非源码生成,确保准确性 run: sbom-gen --target ./dist --format cyclonedx --include-hashes # 生成失败即构建失败,不允许跳过 continue-on-error: false - name: attach-artifact-hash run: sbom-enrich --sbom sbom.json --artifact-hash $(sha256sum ./dist/app) - name: publish steps: - name: sign-artifact # 使用工作负载身份换取短期证书,不使用长期密钥 run: signer sign --artifact ./dist/app --identity workload - name: sign-sbom run: signer sign --artifact sbom.json --identity workload - name: push-with-attestation run: registry push --artifact ./dist/app --sbom sbom.json --attestation provenance.json - name: deploy-verify steps: - name: verify-provenance run: verifier verify-attestation --artifact $IMAGE --policy deploy-policy.yaml - name: verify-hash-match run: verifier assert-hash --attestation provenance.json --artifact $IMAGE - name: policy-check run: policy-engine eval --input sbom.json --rules deploy-rules.yaml # 存在策略禁止组件时失败

几个关键点解释一下。generate-sbom的continue-on-error: false是刻意写的,很多团队在这里默认容忍失败,结果就是某些分支的构建产物长期没有 SBOM,事后完全查不出来。attach-artifact-hash这一步是把产物哈希写回 SBOM,这是实现"产物到清单"反查的关键。部署阶段的verify-hash-match是我认为最不能省的一步,前面说过原因。

5.3 签名与密钥:短期凭据是硬要求

这块单独拿出来说,因为它是整个方案里最容易做错的地方。原则很简单:任何长期有效的签名密钥,只要它存在于 CI 的变量配置里,就应当被视为已经泄露。

正确的做法是用工作负载身份:流水线运行时向身份提供方证明"我是某个仓库的某条流水线",换取一个短期有效的签名证书,证书里绑定仓库、分支、commit 这些信息,有效期通常几十分钟。签名完成后证书自动失效,即使被截获也无法用于其他构建。

这么做还有个额外好处:验证方可以从证书里读出身身份信息,判断"这个声明是不是由期望的那条流水线签发的"。如果用长期密钥,所有构建共用一个密钥,签名里就丢失了身份信息,验证方只能判断"签名有效",无法判断"是谁签的",这个判断的价值就大打折扣。

密钥轮换也要提前规划。即便是短期证书,签发机构的根证书也需要轮换,轮换期间新旧根证书要有一段时间的并存期,否则会出现"旧产物验证不通过"的问题。这个坑我在一个项目里踩过,升级签发机构配置的当天,所有历史产物的验证全部失败,部署流水线全线告警。

5.4 上线之后怎么衡量它有没有用

没有度量的安全建设,半年后一定会被质疑价值。我一般会盯几个指标:

指标计算方式期望方向
SBOM 覆盖率有 SBOM 的发布产物数 / 总发布产物数接近 100%
溯源验证覆盖率部署时执行了验证的次数 / 总部署次数接近 100%
高危漏洞平均修复时长从首次检出到修复上线的中位数持续下降
门禁误报率被人工判定为误报的拦截数 / 总拦截数低于 15%
例外通道使用率走例外流程的构建数 / 总构建数低于 5%

其中我最看重的是门禁误报率。这个指标一旦超过两成,团队对门禁的信任就会崩塌,接着就是各种绕过手段。所以定期抽样复查拦截记录、根据反馈调整规则,比增加新规则更重要。

例外通道使用率是另一个健康度指标。用得太少可能是流程太严导致大家不敢用(然后去偷偷改配置),用得太多说明规则设计有问题。维持在 3% 到 5% 之间,我认为是健康的。

6. 几个我踩过并反复被问到的坑

6.1 清单生成了,然后躺在临时目录里

这个坑我说过一遍还要再说一遍,因为它太普遍了。CI 环境是临时的,构建完机器就回收,任何没被显式上传的东西都会消失。偏偏很多 SBOM 生成工具的默认行为是输出到当前工作目录,而流水线脚本里又没写上传步骤。

排查方法很简单:随便找一个三个月前的发布产物,试着反查它的 SBOM,找不到就说明有这个坑。修复方式是在流水线里把 SBOM 上传做成强制步骤,并且加一个定期巡检任务,抽查最近的发布产物有没有对应的 SBOM,缺了就告警。这个巡检比任何文档都管用。

6.2 一上来就全量阻断,两周后被彻底关掉

我见过不止一次这样的剧情:安全团队上线门禁,第一周全公司范围内阻断了两百多次合并,第二周开始有人在群里问怎么绕过,第三周流水线配置里多了一行跳过检查的参数,第四周这个门禁就名存实亡了。

根本原因是把"存量问题"和"增量问题"混在一起处理。存量漏洞可能有几千个,这些是历史包袱,不可能在短期内解决,用它们去阻断当前的开发工作,只会制造对立。正确的做法是:增量严格把关,存量单独立项、定计划、给资源、按季度推进。这两条线必须分开,用同一套门禁去管,一定失败。

我推荐的具体做法是设置一个基线:把当前所有存量问题打入基线文件,门禁只对基线之外的新增问题生效,同时给存量问题设定一个逐季度缩减的目标。这样既不会阻塞开发,又能持续改善。

6.3 多语言项目的依赖识别总是缺斤少两

一个仓库里同时有后端服务、前端应用、几个脚本工具,语言横跨四五种,这是很常见的形态。这种情况下,任何单一工具都很难把所有生态的依赖都识别全。表现是 SBOM 里只覆盖了主语言,其他语言的组件全部缺失。

我的处理方式是组合使用:选一个覆盖主流生态最全的工具做主生成器,然后针对它支持不好的生态,用对应生态专用的工具补充生成,最后做清单合并。合并时要按包标识符去重,并且保留各自的依赖关系子图。

这里有个细节:合并后的清单必须重新绑定产物哈希,不能沿用合并前的元信息,否则反查关系会错乱。另外合并脚本要纳入版本管理并做测试,不要写成一次性的临时脚本,因为生态在变,工具在升级,这个合并逻辑是需要长期维护的。

6.4 把 SLSA 当成一次性认证项目

最后一个坑是最隐蔽的:把 SLSA 等级当成一个可以"拿到"的证书,投入资源冲到一个等级,然后就不再管了。

SLSA 描述的是构建过程的持续性属性,不是一次性成果。你的构建平台升级、流水线配置变更、签名密钥轮换、甚至换了代码仓库,都可能让实际达到的等级下降。如果验证环节没有持续运行,你根本不知道自己什么时候掉回去了。

务实的做法是把验证做成常态化的自动化检查,定期对最近发布的产物做一次完整验证,包括签名有效性、签发者身份、哈希匹配。验证失败就告警,而不是等到某次审计时才发现问题。同时把"构建平台配置变更"纳入变更管理流程,任何影响构建环境的改动都要重新评估等级影响。

我自己在做这类项目时有个习惯:每个季度做一次"断网演练"式的自查——假设现在有个新披露的高危漏洞,我能不能在三十分钟内查出受影响的产物范围、能不能拿出对应的构建溯源、能不能确认这些产物没有被替换过。如果任何一个环节卡住,那就是下一季度要补的地方。这个自查比任何指标看板都更能反映真实水平。

最后分享一个小技巧:不要试图一次性把三个东西都做到完美。先花两周把 SBOM 的生成和归档做扎实,这是地基;再用一个月把 SCA 的降噪和工单流程跑顺,让团队感受到它是帮手不是负担;最后再动 SLSA,因为它的推进周期最长、组织协调成本最高。顺序反了,前面两件事会因为迟迟看不到效果而被放弃,后面那件也会因为缺少基础数据而落不了地。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 4:16:30

大模型应用开发入门:提示词工程、RAG检索增强与LoRA微调实战

1. 先把大模型这件事想明白,别急着装环境1.1 大模型到底是个什么东西,一句话说透很多人第一次打开对话框,输入“你好”,看到屏幕上哗哗往回蹦字,觉得挺神奇,然后就卡住了——不知道该拿它干嘛。我见过太多人…

作者头像 李华
网站建设 2026/10/28 4:15:22

营销的核心技能不是文案而是判断:从用户洞察到数据复盘

干营销这行马上十年了,被问得最多的问题永远是:"做营销到底需要什么技能?"每次听到这个问题,我都想把一个反常识的结论先摆出来:营销最重要的技能,不是会写文案、会作图、会投广告,而…

作者头像 李华
网站建设 2026/10/28 4:14:56

企业库存优化策略与实施方法全解析

1. 库存优化的核心价值与挑战库存管理是企业运营中永恒的话题。去年我们服务的一家电子制造企业,通过优化库存策略在6个月内将周转率提升了47%,释放了2300万流动资金。这让我深刻意识到,库存不是简单的货物堆积,而是企业现金流的另…

作者头像 李华
网站建设 2026/10/28 4:14:24

卷积神经网络(CNN)核心:卷积层原理与实战指南

1. 卷积层:深度学习的视觉基石第一次接触卷积神经网络(CNN)时,我被它的名字唬住了——听起来像是某种高深的数学运算。直到亲手实现了一个卷积层处理图像数据,才恍然大悟:这不过是个"局部特征提取器"。想象你拿着放大镜…

作者头像 李华
网站建设 2026/10/28 4:13:44

MySQL千万级订单表分页查询优化实战

1. 千万级订单表分页查询的典型痛点那天凌晨3点,我被急促的电话铃声惊醒。生产环境订单查询接口响应时间从200ms飙升到12秒,客服系统已经堆积了上百个投诉工单。打开监控面板,一个简单的分页查询正在疯狂吞噬数据库资源。这就是千万级订单表分…

作者头像 李华
网站建设 2026/10/28 4:12:28

agent-skills:为AI编程助手构建可复用技能系统与TDD实践

1. 从 agent-skills 说起:为什么我们需要给 AI 编程助手装上一套技能系统第一次看到agent-skills这个项目名的时候,我脑子里蹦出来的第一个念头是:这不就是给 AI coding agents 做的一套“技能包”吗?后来翻了一圈它的设计思路和社…

作者头像 李华