依赖清单的构成:SBOM 的概念、标准与生成工具
聊到依赖清单,就绕不开一个词——SBOM。很多人第一次听到它,觉得是个高大上的新概念。其实说白了,SBOM 就是一份「软件物料清单」。你想想看,造一辆汽车需要列出所有零件的型号、批次、供应商,软件也一样。
我在项目中遇到过不少团队,代码跑得好好的,突然被安全团队告知某个底层库有高危漏洞。这时候大家才手忙脚乱地去翻依赖。嗯,如果早有一份完整的 SBOM,这种被动局面完全可以避免。
SBOM 到底是什么?
SBOM(Software Bill of Materials)就是一份结构化的清单,记录了软件中包含的所有组件、库、模块及其版本、许可证、依赖关系等信息。它就像你点外卖时的小票——上面清清楚楚写着每个菜品的名字、分量和价格。
核心价值:SBOM 让软件供应链变得透明。你能一眼看出项目里用了什么、从哪里来、有没有已知风险。
我个人习惯把 SBOM 分成三个层次来看:
- 直接依赖——你主动引入的包,比如在
package.json里声明的那些。 - 间接依赖——你引入的包自己又依赖的包,也叫传递依赖。
- 元数据——许可证信息、版本号、哈希值、来源地址等。
举个例子,你用了一个日志库 log4j,它内部又依赖了 jndi 模块。你的 SBOM 里不仅要记录 log4j,还要把 jndi 也列出来。否则一旦 jndi 出问题,你根本不知道自己的项目是否受影响。
SBOM 标准:SPDX 与 CycloneDX
既然 SBOM 这么重要,那总得有个统一的格式吧?不然你出一份 JSON,我出一份 XML,大家没法互通。目前业界主流的标准有两个:SPDX 和 CycloneDX。
| 对比维度 | SPDX | CycloneDX |
|---|---|---|
| 发起方 | Linux 基金会 | OWASP |
| 侧重点 | 许可证合规、版权信息 | 安全漏洞、依赖关系 |
| 格式支持 | JSON、YAML、RDF/XML、Tag:Value | JSON、XML、Protobuf |
| 漏洞描述 | 通过外部引用关联 | 内置漏洞字段,原生支持 |
| 适用场景 | 法律合规、开源审计 | 安全运维、DevOps 流水线 |
我个人更倾向于在安全场景中使用 CycloneDX。为什么?因为它原生就支持漏洞描述字段,你生成一份 SBOM 后,可以直接在里面标注每个组件关联的 CVE 编号。SPDX 虽然也能做,但需要额外关联外部数据源,多了一步操作。
我的建议:如果你的团队主要关注许可证合规(比如公司要过开源审计),选 SPDX。如果你们更关心安全漏洞和依赖治理,选 CycloneDX。当然,两个都支持也没问题,工具链上可以同时生成。
依赖清单的生成工具
标准定好了,接下来就是怎么生成 SBOM。市面上有不少工具,我挑几个常用的说说。
1. Syft
Anchore 出品的 Syft 是我用得最多的工具。它支持从容器镜像、文件系统、项目源码中生成 SBOM,输出格式支持 SPDX 和 CycloneDX。
# 从容器镜像生成 SBOM
syft nginx:latest -o cyclonedx-json > sbom.json
# 从项目目录生成
syft ./my-project -o spdx-json > sbom.spdx.json
我记得有一次客户要求提供一份完整的依赖清单,项目有上百个微服务。用 Syft 配合脚本批量跑了一遍,半小时就搞定了。要是手动整理,估计得干到天亮。
2. CycloneDX Generator
OWASP 官方出品的工具,专门为 CycloneDX 标准设计。支持多种语言和包管理器:Maven、npm、pip、NuGet 等。
# 为 Maven 项目生成 CycloneDX SBOM
mvn org.cyclonedx:cyclonedx-maven-plugin:makeBom
# 为 npm 项目生成
npx @cyclonedx/bom --output bom.json
3. Trivy
Trivy 本来是漏洞扫描工具,后来也加入了 SBOM 生成功能。它的优势在于可以一条命令同时完成「生成 SBOM + 扫描漏洞」。
# 生成 SBOM 并扫描漏洞
trivy image --format cyclonedx --output result.cdx.json nginx:latest
避坑指南:我曾经遇到过一个坑——用 Trivy 生成的 SBOM 里,间接依赖的版本号有时会丢失。后来发现是镜像层缓存的问题。建议生成后做一次完整性校验,确保每个组件都有版本号。
4. 各语言生态自带的工具
很多包管理器本身就支持导出依赖清单:
- npm:
npm list --json可以输出完整的依赖树 - Maven:
mvn dependency:tree输出依赖树,配合-DoutputFile可以保存 - pip:
pip freeze列出所有已安装的包 - Go:
go list -m all列出所有模块
但这些工具输出的格式不统一,没法直接当 SBOM 用。我一般把它们作为中间数据,再通过脚本转换成标准的 SPDX 或 CycloneDX 格式。
SBOM 的核心知识体系
下面这张图可以帮你快速理解 SBOM 的构成逻辑:
生成 SBOM 的最佳实践
工具选好了,标准定好了,怎么落地?我总结了几条经验:
- 集成到 CI/CD 流水线——每次构建都自动生成 SBOM,而不是等到出问题才去翻。
- 版本化管理——SBOM 文件要和代码一起入库,方便回溯历史版本用了哪些依赖。
- 定期重新生成——即使代码没变,基础镜像或间接依赖可能已经更新了。
- 与漏洞库联动——生成 SBOM 后,自动对接 NVD、GitHub Advisory 等漏洞库做匹配。
一个小技巧:我习惯在 SBOM 文件名里加上构建时间和 Git commit hash,比如 sbom-20250315-abc1234.json。这样排查问题时能精确对应到某个版本的构建产物。