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 质量的头号杀手。我总结了几种常见情况:

  1. 动态加载的组件:比如 Java 的 SPI 机制、Python 的 importlib 动态导入。静态扫描很难抓到。
  2. 容器基础镜像:很多人只扫应用层,忘了扫 FROM alpine:3.18 里的系统包。
  3. 构建时工具:比如 Webpack、Babel、Maven 插件。它们不参与运行时,但如果有漏洞,一样影响构建安全。
  4. 间接依赖的间接依赖:三层以上的传递依赖,很多工具默认不展开。

怎么识别?我有个笨办法,但很有效:

避坑指南:我曾经在一个 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 质量验证流程

说了这么多,咱们用一张图把整个逻辑串起来:

SBOM 质量验证流程 SBOM 质量验证流程 生成 SBOM 格式验证(CycloneDX CLI) 字段完整性检查(版本/哈希/许可证) 依赖关系解析(直接 vs 传递) 缺失组件检测(对比依赖树) 每一步不通过,都返回上一级修正。最终输出一份“可审计”的 SBOM。

你看,整个流程是闭环的。从生成到验证,再到修正,每一步都不能跳过。我见过太多团队只做第一步,后面全凭运气。结果审计时被问得哑口无言。

4.6 总结一下

SBOM 的质量,决定了你的合规工作到底是在“做样子”还是“真落地”。

  • 深度与广度:先扫全,再补细。
  • 依赖关系:直接依赖和传递依赖必须分清楚,用 dependsOn 明确标识。
  • 缺失组件:动态加载、基础镜像、构建工具,这些最容易漏。
  • 验证工具:CycloneDX CLI、SPDX Tools、Dependency-Track,选一个用起来。