4. SBOM 质量与完整性:从“有”到“有用”
说实话,拿到一份 SBOM 不难。难的是,你拿到的这份 SBOM 到底能不能用?
我见过太多团队,工具一跑,生成一个 SPDX 或 CycloneDX 文件,就觉得万事大吉了。结果审计一来,人家问:“你这个组件版本号怎么是空的?”“这个依赖关系是直接依赖还是传递依赖?”“这个哈希值为什么对不上?”——全傻眼。
所以这一章,咱们就聊聊 SBOM 的深度与广度、依赖关系怎么解析、如何揪出缺失的组件,以及有哪些工具能帮你验证 SBOM 的质量。
4.1 SBOM 的深度与广度:你扫到了第几层?
SBOM 的“深度”和“广度”,是两个完全不同的维度。我习惯用一个简单的矩阵来理解:
| 维度 | 定义 | 典型问题 |
|---|---|---|
| 广度 | 覆盖了多少个组件(直接依赖 + 间接依赖) | 有没有漏掉某个子模块? |
| 深度 | 每个组件的信息完整度(版本、哈希、许可证、来源等) | 版本号是精确的还是范围?哈希值有没有? |
举个例子。你扫描一个 Java 应用,广度上可能只扫到了 pom.xml 里列出的 20 个直接依赖。但实际运行时,Maven 拉下来的传递依赖可能有 80 个。这 80 个,才是你真正的攻击面。
深度上呢?有些 SBOM 只写了组件名和版本号,连个 SHA256 都没有。审计时你怎么证明这个组件就是你声称的那个?
我的经验:广度优先,深度跟进。先确保所有组件都被扫到,再逐条补全信息。否则你花大量时间补一个不存在的组件的哈希,毫无意义。
4.2 依赖关系解析:直接 vs 传递,你分得清吗?
依赖关系是 SBOM 的核心骨架。说白了,就是谁依赖谁。
我遇到过最坑的一次,是某个团队把传递依赖当成了直接依赖,结果审计时发现许可证冲突——一个 GPL 的传递依赖被“隐藏”在了三层之后,他们根本没注意到。
依赖关系通常分两种:
- 直接依赖:你主动引入的库。比如你在 package.json 里写了
express@^4.18.0。 - 传递依赖:你的依赖所依赖的库。比如 express 依赖了
body-parser,body-parser 又依赖了bytes。
在 SBOM 中,传递依赖必须通过 dependsOn 字段明确标识。以 CycloneDX 为例:
{
"components": [
{
"bom-ref": "pkg:npm/express@4.18.2",
"name": "express",
"version": "4.18.2",
"type": "library"
},
{
"bom-ref": "pkg:npm/body-parser@1.20.1",
"name": "body-parser",
"version": "1.20.1",
"type": "library"
}
],
"dependencies": [
{
"ref": "pkg:npm/express@4.18.2",
"dependsOn": [
"pkg:npm/body-parser@1.20.1"
]
}
]
}
你看,express 的 dependsOn 里明确写了 body-parser。如果 body-parser 还有自己的依赖,那就继续嵌套。这才是完整的依赖树。
小技巧:解析依赖关系时,我习惯用 npm ls --all 或 mvn dependency:tree 先看全貌。然后再对照 SBOM 里的 dependsOn,看有没有遗漏。
4.3 如何识别缺失组件:那些“隐身”的依赖
缺失组件是 SBOM 质量的头号杀手。我总结了几种常见情况:
- 动态加载的组件:比如 Java 的 SPI 机制、Python 的
importlib动态导入。静态扫描很难抓到。 - 容器基础镜像:很多人只扫应用层,忘了扫
FROM alpine:3.18里的系统包。 - 构建时工具:比如 Webpack、Babel、Maven 插件。它们不参与运行时,但如果有漏洞,一样影响构建安全。
- 间接依赖的间接依赖:三层以上的传递依赖,很多工具默认不展开。
怎么识别?我有个笨办法,但很有效:
避坑指南:我曾经在一个 Go 项目里,用 go mod graph 导出所有依赖,然后跟 SBOM 里的组件列表做 diff。结果发现少了 12 个组件——全是 CGO 调用的 C 库。静态扫描根本扫不到,必须结合二进制分析。
具体做法:
- 用
go mod graph、npm ls --depth=Infinity等命令导出完整依赖树。 - 用脚本对比 SBOM 中的组件列表,找出缺失项。
- 对缺失项进行手动确认:是工具没扫到,还是确实不需要?
4.4 SBOM 验证工具:别信感觉,信工具
手动检查 SBOM 质量?不现实。一个大型项目可能有上千个组件,你一个个看,看到明年。
所以,工具是必须的。我常用的几个:
| 工具 | 用途 | 支持格式 |
|---|---|---|
| CycloneDX CLI | 验证 SBOM 格式、必填字段、依赖关系完整性 | CycloneDX (JSON/XML) |
| SPDX Tools | 验证 SPDX 格式、许可证合规性 | SPDX (Tag/JSON/YAML) |
| Dependency-Track | 持续监控 SBOM 质量,对比基线 | CycloneDX / SPDX |
| Syft + Grype | 生成 SBOM 并扫描漏洞,顺便检查完整性 | CycloneDX / SPDX |
以 CycloneDX CLI 为例,验证一个 SBOM 文件:
# 安装
npm install -g @cyclonedx/cyclonedx-cli
# 验证格式和必填字段
cyclonedx validate --input-file bom.json
# 检查依赖关系是否完整
cyclonedx validate --input-file bom.json --check-dependencies
如果输出 Valid: true,说明格式没问题。但格式没问题不代表质量没问题。你想想看,如果所有组件的版本号都是 *,格式验证能通过,但审计时这就是个大坑。
我的习惯:我会在 CI/CD 里加一个步骤,用 CycloneDX CLI 验证 SBOM,然后用自定义脚本检查几个关键字段:版本号不能是范围、哈希值不能为空、许可证不能是 NOASSERTION。不满足就阻断构建。
4.5 一张图看懂 SBOM 质量验证流程
说了这么多,咱们用一张图把整个逻辑串起来:
你看,整个流程是闭环的。从生成到验证,再到修正,每一步都不能跳过。我见过太多团队只做第一步,后面全凭运气。结果审计时被问得哑口无言。
4.6 总结一下
SBOM 的质量,决定了你的合规工作到底是在“做样子”还是“真落地”。
- 深度与广度:先扫全,再补细。
- 依赖关系:直接依赖和传递依赖必须分清楚,用
dependsOn明确标识。 - 缺失组件:动态加载、基础镜像、构建工具,这些最容易漏。
- 验证工具:CycloneDX CLI、SPDX Tools、Dependency-Track,选一个用起来。