软件供应链安全这件事,真正让人难受的地方不在于概念多,而在于出问题的时候你根本不知道问题从哪儿来。前两年我参与过一次内部排查,某个线上服务被扫出一个高危组件,业务代码里翻了个底朝天也没找到,最后追到基础镜像的某个构建工具链依赖上——那个包从来没被业务代码调用过,却在镜像里躺了半年。这类事情碰到两三次之后,你自然会开始认真对待 SBOM、SCA、SLSA 这几个词,也会开始琢磨怎么把它们真正塞进 CI/CD 流水线,而不是停在 PPT 上。这篇就按我自己的落地顺序讲:先把三个概念各自管什么、不管什么说清楚,再讲流水线里该在哪些卡点做哪些事,最后把踩过的坑和参数选择一并摊开,适合正在做供应链安全治理的研发、安全和平台工程同学对照参考。
1. 三个名词先掰开:各自管什么、不管什么
很多人一上来就把 SBOM、SCA、SLSA 混成一件事,觉得装个扫描器就算做完了供应链安全。实际这三者解决的是完全不同层面的问题,混着做最后一定是三件事都没做扎实。
1.1 SBOM:给软件开一张能对上号的“配料表”
SBOM 是 Software Bill of Materials 的缩写,直译就是软件物料清单。我习惯用食品配料表来类比:一包饼干背面写着用了小麦粉、植物油、食用盐,SBOM 写的就是你这个软件用了哪些组件、什么版本、从哪来、什么许可证、谁依赖谁。它本身不判断好坏,只负责记录事实。
这里有个关键选择要先定下来:SBOM 是源码级生成还是制品级生成。源码级是扫描依赖清单文件,比如package-lock.json、go.mod、pom.xml、requirements.txt,速度快、能精确到声明关系,但有个盲区——构建过程本身会引入东西,比如基础镜像的操作系统包、编译器运行时、打包插件带进来的传递依赖,这些在源码清单里根本看不到。制品级是直接扫最终产物,容器镜像就扫镜像层,二进制就扫二进制,覆盖全但慢,而且有些静态链接的语言拿不到准确的版本号。
我的建议是两个都做,用途分开:源码级放在提交和合并阶段,给开发快速反馈;制品级放在发布阶段,作为归档和审计依据。归档的那份必须是制品级,因为它和你实际部署的东西是同一个哈希,能对得上。
格式上主流就两个:SPDX 和 CycloneDX。SPDX 偏合规审计,字段全、规范严;CycloneDX 偏安全和工具链集成,体积小、生态支持广。选哪个不重要,重要的是组件必须带 PURL。PURL 是组件的统一标识,形如pkg:maven/org.apache.xxx/yyy@1.2.3,有了它,不同工具之间的 SBOM 才能互通,SCA 扫描器才能准确匹配漏洞库。我见过不少团队生成的 SBOM 只有组件名和版本号,没有 PURL 也没有 CPE,拿去喂扫描器匹配率直接掉一半。
还有一点容易忽略:SBOM 要记录依赖关系,不只是组件列表。扁平列表在排查“这个高危包是谁引进来的”时几乎没用,你必须能顺着dependsOn往上追到直接依赖。这个字段在 CycloneDX 里是dependencies,在 SPDX 里是relationships,生成时务必打开。
1.2 SCA:拿着配料表去对照“问题清单”
SCA 是 Software Composition Analysis,软件成分分析。它干的事说白了就是拿 SBOM 里的组件去比对漏洞库,看有没有已知问题。工作原理分三步:解析组件标识(PURL 或 CPE)、到漏洞库里查、按版本区间判断是否受影响。
这里必须先泼一盆冷水:SCA 的准确性高度依赖组件标识的准确性。同样是log4j-core,版本号写成2.14.1能匹配上,写成2.14或者干脆丢了版本号,匹配就会失败或者误判。所以我在上一节反复强调 PURL,就是因为这个环节一旦标识错了,后面全是无用功。
SCA 的误报主要来自三个地方。第一是版本区间判断过宽,很多漏洞库对某些包只写了“小于某版本受影响”,你用的是打了补丁的重新打包版本,版本号没变,照样报。第二是不可达代码,组件在依赖树里,但你的代码从来没调用到那条路径,风险实际上很低,可扫描器不知道。第三是构建工具依赖,测试框架、代码生成器、打包插件带进来的包被算进了运行时依赖,这类误报在 Java 项目里特别多。
漏报同样存在。私有组件、内部二方库不会出现在公共漏洞库里;漏洞库同步有延迟,新披露的漏洞可能几天后才进库;传递依赖如果解析不完整,深层的包根本不在扫描范围内。所以我一直认为 SCA 的产出不应该只有“通过/不通过”二元结果,而是要给每个发现标注可利用性和可达性,让人来判断优先级。CVSS 分数只能作为初筛,真正决定先修哪个的,是这个包在不在运行时路径上、有没有公开的利用代码、暴露面有多大。
1.3 SLSA:证明这锅菜是同一批人按同一流程做的
SLSA 读作“salsa”,是 Supply-chain Levels for Software Artifacts 的缩写。它和前两个不是一类东西——SBOM 和 SCA 关注“用了什么、有没有问题”,SLSA 关注的是“这个产物是不是按预期流程、在可信环境里构建出来的”。
为什么需要这个?因为供应链攻击有两个入口。一个是源码,恶意提交混进主干;另一个是构建环节,构建机被入侵、构建脚本被篡改、依赖在构建时被替换。前一个问题靠代码评审和分支保护解决,后一个问题就得靠 SLSA 这套机制。它的核心产物叫来源证明(provenance),是一份结构化声明,记录这次构建用了哪个仓库的哪个 commit、在哪个构建环境里跑的、构建参数是什么、输入材料的哈希是多少。这份证明会被签名,签名之后就无法伪造或篡改。
SLSA 规范目前把构建轨道分到 L3。L1 要求构建过程自动化并且产出 provenance;L2 要求使用托管构建服务,provenance 经过签名;L3 要求构建环境加固,provenance 由构建平台生成、使用者无法伪造。早期版本还有 L4,强调双人评审和可复现构建,后来在规范演进中被合并调整了,你如果看到旧资料写“四级”,那是历史版本的说法,不用纠结。
落地 SLSA 最容易走进的误区是把它当成一个可以“买”的东西。它不是产品,是规范,需要你用具体工具去实现:provenance 用 in-toto 的 attestation 格式承载,签名可以用密钥对,也可以用基于身份令牌的短期证书方案,验证端用策略引擎做准入判断。这套链路缺任何一环,等级都上不去。
1.4 一张表看清三者的边界与依赖关系
| 维度 | SBOM | SCA | SLSA |
|---|---|---|---|
| 解决的问题 | 软件里有什么 | 这些东西有没有已知漏洞 | 产物是不是可信构建出来的 |
| 输入 | 源码依赖清单或制品 | SBOM 或依赖树 | 构建过程的元数据与材料哈希 |
| 输出 | 组件清单与依赖关系 | 漏洞列表与优先级 | 签名后的来源证明 |
| 消费方 | 安全团队、合规审计、下游客户 | 研发团队、安全运营 | 部署准入、下游集成方 |
| 是否阻断流水线 | 一般不阻断 | 按策略阻断 | 部署阶段阻断 |
| 成熟度 | 高,格式标准化 | 高,但误报治理是长期活 | 中,工具链仍在演进 |
三者是递进关系:SBOM 是数据基础,SCA 消费 SBOM 做判断,SLSA 保证 SBOM 和产物本身没被掉包。缺了 SBOM,SCA 就是无源之水;缺了 SLSA,你手里的 SBOM 可能描述的根本不是部署上去的那个东西。
2. 落地前的设计:先定风险模型,再定工具链
我见过太多团队一上来就比价采购工具,结果买回来发现和自己的流水线根本接不上,或者扫出来的东西没人处理。工具是最后一步,前面得先把风险模型和卡点位置想清楚。
2.1 从“你最怕什么”倒推要做的三件事
供应链风险粗分四类,对应不同的技术手段。
第一类是引入已知漏洞组件,这是最高频的,对应 SCA。第二类是许可证冲突,尤其是对外交付的产品里混进了传染性许可证的库,对应 SBOM 的许可证字段分析。第三类是依赖投毒和仿冒包,攻击者发布一个名字和知名库差一个字母的包,或者劫持维护者账号发新版本,这类靠 SBOM 加包源校验、依赖白名单来防。第四类是构建产物被篡改,从源码到制品的链路上任何一个环节被动手脚,对应 SLSA 加签名验证。
你要做的是给这四类排个序。如果是面向企业客户交付的产品,许可证和 SBOM 交付往往是硬性合同要求,优先级最高。如果是互联网服务,漏洞响应速度是第一位的,SCA 要先跑起来。如果你们的制品会被下游大量集成,那签名和 provenance 就是刚需。排序不同,落地顺序完全不同,别照搬别人的方案。
2.2 把三个动作嵌进流水线的哪几个卡点
流水线上有四个天然卡点,我按投入产出比从高到低排:
提交与合并阶段做增量检查。只扫这次改动新增或升级的依赖,判断是否命中高危、是否在禁用清单里。这个阶段必须快,超过一分钟开发就有意见,所以不做全量、不做镜像扫描。
构建完成阶段做全量 SBOM 生成和 SCA 扫描。这时候依赖树是完整的,扫描结果最准,耗时可以放宽到几分钟,但要有缓存机制,漏洞库不要每次重新下载。
发布阶段做签名和 provenance 生成。产物一旦推送制品库,就按摘要签名,同时把 SBOM 作为 attestation 一起附上。
部署阶段做准入验证。检查镜像有没有签名、provenance 里的构建来源是不是白名单内的流水线、源码 commit 是不是来自受保护分支。
有个细节必须提醒:部署验证要按镜像摘要而不是标签。标签是可变的,同一个latest今天指向这个镜像明天指向那个,按标签验证等于没验证。
2.3 工具选型的几个硬指标
选型别只看功能列表,我一般盯这几条:
| 指标 | 为什么重要 | 判断方法 |
|---|---|---|
| 格式支持 | 决定 SBOM 能否跨工具流转 | 是否同时支持 SPDX 与 CycloneDX 导入导出 |
| 离线漏洞库 | 内网环境能否用 | 是否支持本地库同步与增量更新 |
| 误报抑制 | 决定治理成本 | 是否支持按包、按路径、按规则抑制并设到期时间 |
| CI 集成方式 | 决定接入成本 | 是否有独立命令行工具,而非只能走网页平台 |
| 扫描性能 | 决定能否进主干流水线 | 千级组件项目的全量扫描是否在两分钟内 |
| 证明格式 | 决定 SLSA 能否落地 | 是否产出标准 in-toto attestation |
方案上大致三类:商业 SCA 平台功能全、误报治理成熟,但按项目或按扫描次数计费,规模化之后成本不低;开源工具组合灵活、可离线,但需要自己搭漏洞库同步和结果聚合;代码平台内置的扫描最省事,但格式和策略定制能力通常偏弱。我的经验是核心流水线用开源命令行工具保证可控性,治理层用平台做聚合和工单流转,两头各取所长。
2.4 分三阶段推进,别一口气上全套
第一阶段只做可见性。生成 SBOM、跑 SCA,结果进报表,不阻断任何流水线。这个阶段的目标是摸清家底:一共有多少制品、平均每个制品多少组件、高危存量多少、哪些团队问题最集中。跑两周你就有数据支撑后面的决策了。
第二阶段开始阻断,但只阻断高危且确认可利用的。策略要留白名单通道,白名单必须带责任人和到期时间,到期自动失效。这个阶段最容易被研发抵触,所以阻断信息必须写清楚“哪个包、什么漏洞、升级到哪个版本能解决”,而不是甩一个 CVE 编号。
第三阶段上签名和准入。先把签名做起来,验证端以审计模式运行,观察两周误拦率,再切成强制。这一步的技术难点不在签名本身,在于密钥或身份的管理,以及制品库是否支持存储 attestation。
3. 实操拆解:从生成 SBOM 到部署前验签
下面这套流程是我目前项目里在跑的简化版,工具用的是常见的开源实现,你可以按自己的技术栈替换,思路是通用的。
3.1 第一步:确定 SBOM 的格式、粒度和生成时机
我选 CycloneDX 的 JSON 格式,原因是体积比 XML 小、工具支持广、attestation 集成方便。源码级和制品级都生成,但归档只认制品级。
源码级生成,在构建前的准备阶段跑:
syft dir:. -o cyclonedx-json=sbom-source.cdx.json --exclude "**/test/**"制品级生成,在镜像构建推送之后跑:
syft registry:registry.internal/app/service@sha256:abc123... \ -o cyclonedx-json=sbom-image.cdx.json \ -o spdx-json=sbom-image.spdx.json这里注意两点。一是镜像要用摘要而不是标签,否则你扫的和你发布的可能不是同一个东西。二是同时输出两种格式,CycloneDX 给内部工具消费,SPDX 留给有合规要求的交付场景,成本几乎为零。
SBOM 生成后要做一次轻量校验,至少检查三件事:组件数量是否在合理区间(突然从八百掉到两百,大概率是解析失败)、PURL 覆盖率是否超过九成、依赖关系字段是否非空。我写过一个十行的脚本做这个检查,拦下过好几次因为构建脚本改动导致 SBOM 变成空壳的情况。
归档策略上,SBOM 和制品放同一个存储位置,保留期与制品一致。建议额外做一件事:把 SBOM 的哈希写进制品库的元数据,这样后续能验证 SBOM 本身没被篡改。
3.2 第二步:SCA 扫描的阈值设计与阻断策略
扫描直接消费上一步的 SBOM,不要重新解析依赖,这样能保证扫描的对象和归档的对象是同一份数据。
grype sbom:sbom-image.cdx.json \ --fail-on high \ --only-fixed \ -o json > sca-result.json--only-fixed这个参数很关键,它只报告有修复版本的漏洞。没有修复版本的漏洞报了也没用,研发改不动,只会制造噪音。这类漏洞应该走另一条流程:记录、评估缓解措施、定期复核,而不是卡在流水线上。
阻断阈值我按环境分:主干分支合并要求无高危,发布流水线要求无严重,定时全量扫描允许存在中危但要有工单跟踪。这个分级是有意为之,合并阶段卡太死会导致研发绕过流程,发布阶段卡太松又失去意义。
白名单配置要结构化,不能只写 CVE 编号:
ignore: - vulnerability: CVE-2024-XXXXX package: "pkg:maven/com.example/legacy-util@2.3.1" reason: "该组件仅用于离线数据迁移工具,不在服务运行时路径" owner: "platform-team" expires: "2026-06-30"到期时间这一条必须有。我见过太多白名单变成永久豁免,三年后翻出来发现当初写理由的人已经离职了。
关于扫描耗时,给个实测参考:一个约一千二百个组件的 Java 服务,本地漏洞库已缓存的情况下,grype 全量匹配大约五十到九十秒;如果每次都重新下载漏洞库,会多出两到三分钟。所以流水线里一定要把漏洞库目录做成缓存,每日定时同步一次即可。另外建议加一层增量逻辑:如果本次提交没有改动依赖清单文件,跳过全量扫描,只做制品级快速校验。
3.3 第三步:生成来源证明并签名
这一步是整个链路里技术门槛最高的。先明确要证明什么:构建用的源码仓库地址、commit 摘要、构建流水线标识、构建参数、输入材料的哈希。这些信息大部分能从流水线环境变量里拿到,需要自己拼装成 in-toto 的 attestation 格式。
签名方式有两种选择。基于密钥的方式,自己生成密钥对,私钥放密钥管理服务,流水线运行时拉取,好处是不依赖外部身份系统,内网可用;坏处是密钥轮换和权限管理要自己做。基于身份令牌的方式,流水线用自身的身份令牌换取短期证书,签名记录进透明日志,好处是没有长期密钥、可审计性强;坏处是对流水线的身份配置有要求,内网环境可能走不通。
我目前的项目用密钥方式,因为构建全在内网。签名命令大致是这样:
cosign attest --predicate provenance.json \ --type slsaprovenance \ --key kms://internal/signing-key \ registry.internal/app/service@sha256:abc123... cosign sign --key kms://internal/signing-key \ registry.internal/app/service@sha256:abc123...两个命令的区别:attest把 provenance 作为 attestation 附在镜像上,sign是给镜像本身签名。两个都要做,缺一个验证端就没法建立完整信任链。
这里有个实操细节值得单独说:provenance 里的材料哈希必须包含 SBOM。也就是说,你签的不只是镜像,还有“这个镜像对应这份 SBOM”这个事实。这样下游拿到镜像、SBOM、provenance 三样东西,可以互相校验,任何一样被换掉都会验证失败。
3.4 第四步:部署准入时验证签名与证明
验证逻辑放在集群的准入控制层,镜像要进集群先过一道检查。检查项至少三条:签名有效且来自受信任的密钥、provenance 里的构建流水线在白名单内、provenance 里的源码仓库和分支符合预期。
策略用声明式的方式写,伪代码大致是这个意思:
拒绝 如果 镜像未签名 拒绝 如果 签名密钥不在 trusted_keys 列表 拒绝 如果 provenance.builder.id 不在 allowed_builders 拒绝 如果 provenance.source.repository 不在 allowed_repos 拒绝 如果 provenance.source.commit 对应的分支不是 main 或 release/*上线节奏上,先跑审计模式两周,把所有本该拒绝的请求记录下来但不实际拦截,观察误拦率。误拦通常来自三个原因:历史镜像没签名、临时调试镜像走了非标准流水线、多集群共用策略但某些集群的构建源不同。这些问题在审计期解决完,再切强制模式。
3.5 一条完整流水线的骨架示例
把上面几步串起来,流水线结构大致如下:
stages: - prepare - build - analyze - publish - verify prepare: script: - syft dir:. -o cyclonedx-json=sbom-source.cdx.json - ./check-sbom.sh sbom-source.cdx.json - grype sbom:sbom-source.cdx.json --fail-on critical --only-fixed build: script: - build-image --tag $IMAGE_TAG - push-image $IMAGE_TAG - export IMAGE_DIGEST=$(get-digest $IMAGE_TAG) analyze: script: - syft registry:$IMAGE_REF@$IMAGE_DIGEST -o cyclonedx-json=sbom-image.cdx.json - grype sbom:sbom-image.cdx.json --fail-on high --only-fixed -o json > sca.json - ./sbom-policy-check.sh sbom-image.cdx.json publish: script: - ./gen-provenance.sh > provenance.json - cosign attest --predicate provenance.json --type slsaprovenance --key $KEY $IMAGE_REF@$IMAGE_DIGEST - cosign sign --key $KEY $IMAGE_REF@$IMAGE_DIGEST - archive-sbom sbom-image.cdx.json $IMAGE_DIGEST verify: script: - cosign verify --key $PUB $IMAGE_REF@$IMAGE_DIGEST - cosign verify-attestation --type slsaprovenance --key $PUB $IMAGE_REF@$IMAGE_DIGEST - ./check-provenance-policy.shanalyze和publish之间不要有手动审批环节,否则整条链路的自动化就断了,provenance 的可信度也会打折扣。要控制就在prepare阶段控制,也就是代码合并的时候控制住。
4. 踩坑记录:这些问题我在实际项目里都遇到过
4.1 SBOM 相关的三个高频坑
第一个坑是组件数量爆炸。一个中等规模的 Java 服务,SBOM 里出现三四千个组件是正常的,因为 Maven 的传递依赖非常深。更麻烦的是同一个库的多个版本共存,比如三个模块分别引了不同版本的日志库,SBOM 里就会出现三条记录。这时候做漏洞匹配会重复报告,治理时要先做去重和版本收敛,把“同一 PURL 不同版本”的条目合并展示,否则研发看到一屏几十条相同漏洞直接就不想看了。
第二个坑是版本号丢失。Go 的静态编译二进制、C 语言静态链接的库、某些经过裁剪的运行时,扫出来的组件只有名字没有版本号。这种情况 SCA 基本无解,只能靠构建时的依赖清单做补充。我的处理办法是在构建阶段同时保留依赖解析的原始输出,制品级 SBOM 拿不到版本的组件用它来补。
第三个坑是许可证识别错误。双许可证的库经常只识别出其中一个,文件头缺失的库直接标成未知。对外交付场景这个错误代价很高,所以许可证字段我建议人工复核 Top 50 的高频依赖,剩下的按“未知即需审查”的原则处理,不要把未知当作安全。
4.2 SCA 误报与漏报的治理手法
误报治理的核心思路是按包治理而不是按漏洞治理。同一个包上的十个漏洞,往往是因为这个包整体版本太旧,升级一次全解决。所以工单应该开给“升级某某包到某版本”,而不是开给十条 CVE。我们做过统计,按包聚合之后工单数量能降六成以上,研发的接受度完全不一样。
预发布版本误判是另一个高频问题。某些漏洞库的版本区间写得不严谨,1.2.0-beta会被判定在受影响范围内,实际这个版本根本没包含问题代码。处理方式是给预发布版本单独设一条规则,先降级为低优先级,人工确认后再决定。
漏报方面,私有组件的漏洞只能靠自己积累。我们的做法是把内部二方库也纳入 SBOM,并且维护一个内部漏洞登记表,发现问题的组件版本手工登记进去,扫描时合并两套数据源。这件事前期投入不小,但内部库复用率高,收益是长期的。
4.3 SLSA 等级提升过程中的现实阻力
最大的阻力是构建时长。L3 要求构建环境隔离,意味着每次构建都是干净环境,依赖缓存全部失效,一个原本八分钟的构建可能变成二十分钟。解决办法是把依赖缓存做成独立于构建环境的制品,构建开始前拉取,构建结束后校验哈希,这样既满足隔离要求又不至于每次都从零下载。
第二个阻力是多流水线统一 provenance 格式。一个团队可能同时有容器构建、二进制构建、前端静态资源构建,每种构建的元数据结构都不一样。我的建议是先定一个内部统一的 provenance 模板,只保留验证端真正会用的字段,其余字段留空,等验证策略稳定了再逐步补全。想一步到位把所有字段填满是典型的过度设计。
第三个阻力是制品库不支持 attestation 存储。签名和证明要跟着制品走,老一代的制品库可能只支持存储镜像,没有附件的概念。这种情况有两个绕法:一是把 attestation 作为独立的制品类型存进同一个仓库,用摘要关联;二是在部署验证时从透明日志或独立的证明服务里查。前者改动小,我更推荐。
还有一个软性阻力:团队会觉得“我们又不是攻击目标”。这话在平时说没问题,但供应链攻击的特点就是批量化和自动化,攻击者扫的是整个生态里的薄弱环节,不针对具体某一家。真出事的时候,你能不能在两小时内说清楚“哪些产物用了受影响版本、分别部署在哪儿”,取决于平时有没有把 SBOM 和 provenance 建起来。
4.4 问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| SBOM 组件数骤降 | 依赖解析失败或扫描路径错误 | 检查构建日志,校验 PURL 覆盖率 |
| 同一组件重复报告 | 多版本共存未去重 | 按 PURL 聚合,推动版本收敛 |
| 高危漏洞修不动 | 无修复版本 | 转人工评估,记录缓解措施并设复核期 |
| 签名验证失败 | 按标签而非摘要验证 | 统一改用镜像摘要 |
| provenance 校验不过 | 构建流水线标识变更 | 更新验证策略白名单,审计期观察 |
| 扫描耗时过长 | 漏洞库未缓存 | 每日定时同步,流水线内复用缓存目录 |
| 准入误拦历史镜像 | 存量镜像未签名 | 设例外清单并制定回补计划 |
5. 长期运营:让这套东西不变成摆设
5.1 漏洞响应的 SLA 与责任划分
工具跑起来只是开始,真正决定效果的是响应机制。我按严重程度定四级 SLA,这个分级考虑了两个维度:漏洞本身的严重程度,以及是否已有公开的利用方式。
| 等级 | 判定标准 | 修复时限 | 处置方式 |
|---|---|---|---|
| 紧急 | 严重级别且有公开利用 | 七十二小时内 | 立即修复或下线相关功能 |
| 高 | 严重级别或高危且有利用 | 十五天 | 排入当前迭代 |
| 中 | 高危无利用或中危有利用 | 三十天 | 排入下个迭代 |
| 低 | 其余 | 九十天 | 随版本升级处理 |
责任划分上,组件升级由业务团队负责,因为只有他们知道升级会不会破坏功能;扫描策略和白名单审批由安全团队负责;流水线集成和工具维护由平台团队负责。这三方职责不清是治理失败最常见的原因,工具报警了没人认领,两个月后还是那批漏洞。
5.2 值得盯的几个度量指标
不要用“扫描次数”“发现漏洞总数”这种虚荣指标,它们只会鼓励刷量。我盯的是这几个:
SBOM 覆盖率,即有 SBOM 归档的制品占全部制品的比例,目标接近百分之百。高危组件存量,按周统计趋势,看的是收敛速度而不是绝对值。平均修复时长,从漏洞被发现到修复上线的中位数,这个指标最能反映流程是否通畅。准入拦截率,部署阶段被拦下的请求占比,太低说明策略形同虚设,太高说明流程有问题需要调整。白名单存量与到期率,过期未处理的白名单数量是最能暴露治理松懈的信号。
5.3 几条个人经验
先说一个反直觉的:不要追求把所有漏洞清零。依赖树的深度决定了这件事不可能做到,强行清零的结果一定是大范围白名单豁免,反而失去治理意义。合理的目标是把高危存量控制在一个稳定低位,并且保证新引入的高危能被及时拦住。
第二个经验是把 SBOM 当作对外交付物来做。很多客户现在会主动要 SBOM,如果你平时就在生成、格式规范、字段完整,交付时直接导出就行;如果是临时抱佛脚,光是把几千个组件的许可证理清楚就要两周。这件事提前做的边际成本极低。
第三个经验是签名密钥的管理比签名本身重要。我见过密钥直接写在流水线环境变量里、所有项目共用一个密钥的情况,这种签名提供的信任度接近于零。密钥至少要按环境隔离,私钥放密钥管理服务,访问权限最小化,轮换周期定下来并真的执行。
最后一个提醒:这套体系的价值在出事那一刻才体现。平时它只是流水线里多出来的几十秒和一屏报表,看起来是负担;但当你需要在半小时内回答“这个漏洞影响我们哪些服务”的时候,有没有 SBOM 和 provenance,差别就是半小时和三天。我自己的做法是每个季度做一次桌面推演,随便挑一个刚披露的漏洞,走一遍从查询影响到确认修复的完整流程,把卡住的地方记下来改进。演练比任何规范文档都管用。