
去年年底我们在给一个面向政企客户的交付项目做安全评审时客户方的安全负责人问了一句“你们这个系统软件物料清单能出吗”当时团队里几个人都愣了一下。后来花了一周多时间把SBOM从概念补到落地经历了手动拼装、工具选型、格式踩坑、 CI/CD 集成等一系列过程才真正把这件事理顺。现在回头看SBOM 这东西看似只是一个“软件成分清单”但真正做起来涉及的问题比你想象的多得多——格式怎么选、工具怎么配、扫描结果怎么解释、审计时怎么对齐每一环都有坑。这篇文章我就以这次实际落地的经验为线索从一个软件开发者视角把 SBOM 的完整生成过程讲清楚。1. SBOM 到底是什么先搞懂它的底层逻辑1.1 软件物料清单的定义与核心价值SBOMSoftware Bill of Materials软件物料清单直译过来就是“软件物料清单”。它本质是一份机器可读的清单文件用于描述软件系统中包含的所有组件、库、依赖及其元数据。你可以把它类比成食品包装上的配料表只不过换成软件形态一个应用系统里到底用了哪些开源组件、什么版本、来自哪里、许可证是什么、有没有已知漏洞这些信息都集中记录在 SBOM 文件里。很多开发者第一次接触 SBOM 时会觉得它跟“依赖清单”比如 package.json、requirements.txt很像。这里要纠正一个误区依赖清单是对“项目直接引用的依赖”的描述而 SBOM 是对“最终交付物里实际包含的完整成分”的描述。两者最大的区别在于SBOM 要尽可能覆盖传递依赖依赖的依赖同时还要包含组件来源、许可证信息、哈希校验值、组件间的依赖关系等更丰富的信息。它解决的核心问题是“供应链透明度”。比如你引用了某个开源库这个库里又嵌套了另一个组件那个组件可能存在已知安全漏洞或者它的开源许可证不兼容你的商用场景——没有 SBOM这些问题基本只能在事故发生后追查有了 SBOM就能做到事前识别、事中追踪、事后溯源。1.2 SBOM 里的字段到底长什么样我曾经花了不少时间研究 SBOM 的标准格式。当前国际上主流的两个标准是 SPDX 和 CycloneDX二者对“组件描述”的侧重点略有不同但核心字段基本一致。下面这个表格是我整理的最常用字段可以作为生成 SBOM 时的验收依据字段含义说明重要性组件名称比如 log4j、spring-boot-starter-web必填组件版本精确到具体版本号不可模糊必填供应商/作者是谁发布的这个组件建议填写许可证组件的开源许可证类型如 Apache-2.0必填依赖关系组件之间谁依赖谁是否存在嵌套必填下载/来源地址组件的获取地址仓库地址等建议填写文件哈希SHA-256 或 SHA-1 校验和用于完整性校验建议填写已知漏洞ID部分工具会自动关联 CVE 等信息可选1.3 SPDX 和 CycloneDX 两个标准怎么选我直接说结论如果项目面向欧美客户或需要对接国际供应链合规审查优先选择 SPDX它是 Linux Foundation 主导的格式被各大厂商支持如果项目主要面向安全分析、漏洞管理CycloneDX 会更合适它对漏洞数据VEX的支持更成熟也是 OWASP 社区推荐的格式。从生成工具的兼容性来看两者都有大量工具支持。比较稳妥的做法是生成 CycloneDX JSON 格式用于安全扫描生成 SPDX 格式用于合规存档。我们最终就是这么干的。提示有些工具能直接把 CycloneDX 转成 SPDX但转换过程可能出现字段丢失建议在使用前做一次差异对比别盲目转格式。2. 六大主流生成方案实操对比2.1 从项目依赖清单手工构建兜底方案但别依赖它如果你只是临时需要一份 SBOM 给客户看又没有现成工具可以手工拼一份简单的清单。以 Java 项目的 Maven 为例执行mvn dependency:tree可以列出完整依赖树再配合mvn dependency:list输出依赖列表把输出整理成结构化文件。这种方式的问题很明显手动整理容易遗漏、易出错而且当项目有上百个依赖时根本没法维护。它只适合作为“兜底”方案不适合作为正式流程。我曾经在演示环境临时事态下用过一次过程确实痛苦而且生成的清单很难通过客户的格式校验。2.2 CycloneDX 官方插件Java 和 Node 生态的最稳选择CycloneDX 官方维护了多个生态的生成插件我用得最多的是 Maven 插件和 Node 工具。Java 项目Maven的执行方式如下以生成聚合所有模块的 BOM 为例mvn org.cyclonedx:cyclonedx-maven-plugin:2.7.11:makeAggregateBom执行后会在target/目录下生成bom.json和bom.xml。这个插件会解析 Maven 依赖树把直接依赖和传递依赖都写进去并尽量补齐许可证信息。需要注意的是makeAggregateBom 和 makeBom 的区别在于前者会聚合整个多模块项目后者只生成当前模块在多模块场景下千万别用错了。Node 生态npm 或 yarn使用官方 CLI 工具npx cyclonedx/cyclonedx-cli --input package-lock.json --output bom.json --output-format json这里有个关键点必须是package-lock.json而不是package.json因为 npm 的 lock 文件里才锁定了完整的传递依赖树。实测用 package.json 生成的 SBOM 会漏掉大量传递依赖基本不可用。2.3 Syft容器和目录扫描的全能选手如果你需要扫描的不是某个具体语言生态的项目而是整个目录甚至是容器镜像Syft 是目前最顺手的工具之一。Anchore 开源支持容器镜像和文件系统两种扫描模式。在命令行中安装并扫描本地目录curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin syft packages ./myapp --output cyclonedx-json --file bom.cdx.json扫描容器镜像的方式更常用前提是本机有 Dockersyft packages docker:nginx:latest --output cyclonedx-json --file nginx-bom.jsonSyft 对大部分主流语言Python、Java、Go、Ruby、JavaScript 等和操作系统包apk、deb、rpm都有很好的解析能力是目前我评测下来识别率较高、误报率较低的开源工具。它的处理速度也能接受一个中型 Spring Boot 项目整体扫描大约在 10 秒以内。2.4 Trivy漏洞扫描和 SBOM 生成一把抓很多团队本来就在用 Trivy 做镜像漏洞扫描其实它也能直接生成 SBOMtrivy image --format cyclonedx --output trivy-bom.json nginx:latest trivy fs --format spdx-json --output fs-bom.json ./myprojectTrivy 的 SBOM 生成能力虽然不如 Syft 细致但它有一个优势生成结果可以直接用于漏洞扫描不需要二次加工。如果你团队已经有 Trivy可以暂时不用引入 Syft。不过实测发现Trivy 以 SPDX 格式生成时对某些组件的“来源”信息supplier补得不如 Syft 完整如果对合规要求严格建议两边生成结果做一次交叉比对。2.5 SPDX SBOM Generator微软开源的稳妥选项微软提供了一个专门的 SBOM 生成工具spdx-sbom-generator。对 Java、Go、Python、JavaScript 等生态都有支持。go install github.com/microsoft/spdx-sbom-generatorlatest cd /path/to/project spdx-sbom-generator -p生成结果以 SPDX 格式输出。个人体感是这个工具的稳定性不错但对新语言生态比如 Rust、Swift的支持略显滞后生成速度也不算快。适合在团队已经有明确 SPDX 交付要求的场景下使用。2.6 商业方案和云服务需要正规支持时的选择除了开源工具市面上还有不少商业/云服务方案比如 Snyk、FOSSA、Sonatype 等它们能提供更完整的供应链安全管理包括 SBOM 生成、漏洞关联、许可证合规审计等能力。选择商业方案的核心考量是当你的项目要应付严格的外部审计或组件数量极大、合规要求极高时商业工具自带的漏洞库和许可证知识库价值很大。但如果只是“能生成 SBOM”这一基本诉求开源工具完全足够不需要花这个钱。我在一个金融行业客户的项目中体验过商业工具的商业规则库确实省心但价格不低没必要一上来就上商业方案。2.7 工具对比选型速查工具适用场景输出格式是否支持 YAML许可证合规信息上手难度CycloneDX Maven 插件Java 项目CycloneDX JSON/XML否好低cyclonedx/cyclonedx-cliNode 项目CycloneDX JSON/XML否好低Syft目录、容器镜像、多语言CycloneDX、SPDX 等是中中Trivy容器镜像、文件系统CycloneDX、SPDX是中低spdx-sbom-generatorJava、Go、Python、JSSPDX否中中Snyk/FOSSA 等商业方案企业级供应链安全多格式支持强低3. SBOM 生成后的应用价值与实际操作流程3.1 完整生成流程从“能不能出”到“秒级流水线”我在第一个项目里理出了一条标准化的 SBOM 生成流程分享给大家参考。这条流程适合把 SBOM 集成到构建阶段每次发布自动生成而不是临时抱佛脚。第一步在构建服务器上安装方案对应的工具。我们用的是自建 Jenkins 和 GitHub Actions 混合环境。工具建议固定版本不要用 latest否则某天工具升级可能会导致生成结果差异。第二步在构建阶段调用 SBOM 生成命令。比如对 Java 项目在 Maven 打包后执行 cyclonedx-maven-plugin对前端项目安装依赖后执行 cyclonedx-cli。第三步将 SBOM 文件存档。归档位置建议与制品artifact放一起例如在制品仓库的同一版本目录下或者直接作为构建产物的一部分。第四步将 SBOM 文件推送至集中管理平台。企业环境一般会有制品仓库如 Nexus、Artifactory或统一的安全管理平台把 SBOM 上传上去即可形成“每个制品对应一份 SBOM”的映射。第五步定期使用 Trivy 或 Grype 对 SBOM 做漏洞关联扫描。这条流程跑通之后后续每次发布都能自动获得最新版本的 SBOM 文件不再需要人工干预。我现在的基本要求只有一句话构建成功后如果没有 SBOM 文件发布视为失败。3.2 用 SBOM 做漏洞管理以 Trivy 为例SBOM 本身只是清单它更大的价值体现在“清单 漏洞库”的组合上。Trivy 可以直接消费 SBOM 文件做扫描trivy sbom --input trivy-bom.json --severity HIGH,CRITICAL这条命令会读取 SBOM 里面的组件列表并关联漏洞数据库。需要注意Trivy 对 SBOM 的检测效果取决于 SBOM 本身的完整性。如果 SBOM 里缺少了版本号、缺少了组件来源等关键信息漏洞匹配的准确率会大幅下降。如果你想用 Syft 生成的 SBOM 做漏洞扫描对应的扫描工具是 Grypegrype sbom:./bom.cdx.jsonGrype 与 Syft 配合很默契因为两者出自同一团队Anchore数据格式和识别逻辑天然兼容。3.3 许可证合规SBOM 带给法务的“体检报告”在软件采购和交付环节许可证风险往往是容易被忽略的部分。如果一个商用系统里引用了 GPL 协议的库可能意味着你的源代码在某些范围内有被开源要求的风险。通过 SBOM 里的 license 字段可以提取出项目中所有组件的许可证列表甚至配合策略规则自动标记“高风险许可证”。比如 CycloneDX 的 JSON 里每个 component 下会有licenses字段里面有许可证名称和许可证 ID。有条件的团队可以用 ORTOSS Review Toolkit一类的工具基于 SBOM 做更细粒度的许可证合规分析在发布前就发现许可证冲突。我之前在某欧洲项目中遇到过 GPL 和商业闭源软件混用的情况法务团队要求补 SBOM就是对许可证这块拿不准最后是靠 SBOM 的自动化分析定位到了具体依赖。3.4 SBOM 与变更追踪每次升级都看得到影响SBOM 还有一个容易被忽略的应用场景版本变更影响分析。举个例子项目发布后客户环境反馈某个功能异常开发人员发现是底层依赖库行为不一致导致。传统做法是逐个排查依赖版本。有了 SBOM直接对比两个版本如 v1.0 和 v1.1的 SBOM用 diff 工具就能找出新增、删除、变更的组件快速定位可能引入问题的组件。实际使用中可以用jq对 JSON 格式的 SBOM 做过滤对比也可以用 Python 脚本比对 CycloneDX 的组件列表。这一招在排查“升级后功能异常”的场景下非常高效。4. 实操过程中会遇到的坑与排障速查4.1 坑一依赖锁文件缺失导致成分严重不全这是遇到最多的问题。很多开发者在 Node 项目里根本没有认真维护package-lock.json导致 SBOM 工具只能读取package.json中的直接依赖传递依赖大量丢失。解决方案在生成 SBOM 前确认项目里存在合法的依赖锁文件。对于 npm 是package-lock.json对于 pip 是poetry.lock或requirements.txt最好由 pip-compile 维护对于 Go 是go.sum配合go.mod。如果没有锁文件第一步不是生成 SBOM而是先把依赖锁定机制建立起来。4.2 坑二容器镜像扫描 vs 目录扫描结果不一致同一套代码用Syft 扫描目录和Syft 扫描镜像得到的结果可能差别很大。这是因为容器镜像里除了项目代码中的依赖之外还包含操作系统层的软件包如 glibc、openssl而目录扫描默认不包含 OS 级组件。这个差异不是 Bug而是两种视角。严格来说交付物是什么就扫什么。如果你交付的是容器镜像那就以镜像扫描结果为准如果交付的是源码包目录扫描更准确。客户如果要求 SBOM 与交付物一致我们一般选择“镜像扫描”因为它更接近运行时真实情况。4.3 坑三许可证信息大量缺失很多组件在 Maven 中央仓库或 npm 仓库里的元数据并不完整导致生成的 SBOM 中许可证字段为空。许可证字段缺失在合规审计中很致命。处理建议先用工具自动生成再用“许可证补全”脚本或人工审核补上关键组件的许可证信息。如果项目组件数量大超过 200 个可以考虑用 ScanCode Toolkit另一个开源工具对本地组件源码做许可证识别再与 SBOM 合并。4.4 坑四同一组件多种命名导致漏洞误判同一个底层库在 Maven 里可能叫commons-logging在扫描工具中被解析成Apache Commons Logging如果 SBOM 工具和漏洞库的命名空间对不上漏洞匹配就可能遗漏。这个问题没有万能解我的经验是尽量用同一套工具链生成工具和漏洞扫描工具出自同一团队比如 Syft Grype或 Trivy 生成 Trivy 扫描减少跨工具命名不一致带来的匹配误差。4.5 常见问题排查速查表现象可能原因解决方案SBOM 缺少传递依赖没有使用锁文件解析确认项目存在 package-lock.json / go.sum 等镜像扫描结果异常大包含了数十个 OS 级包确认需求是完整镜像还是仅应用依赖组件漏洞匹配率低SBOM 信息不完整检查版本号、供应商、许可证是否齐全许可证字段大量空白上游仓库元数据缺失结合 ScanCode 或人工补充生成耗时过长依赖树过大或工具版本过旧评估合理范围或按模块拆分生成4.6 独家实战心得生成后必须做的三件验证SBOM 生成完毕不能直接收工。我每次都会做三件事来验证质量第一件抽样人工比对。随机挑 5 到 10 个组件在仓库中核对版本号是否正确避免解析错位。第二件用漏洞扫描工具跑一遍看漏洞命中数是否在合理范围。如果某个项目明明已知有高危漏洞但 SBOM 扫描一个都没报说明 SBOM 很可能是残缺的。第三件检查格式是否合规。用官方 Schema 校验工具跑一下比如 CycloneDX 的 schema 在 GitHub 上有发布一个简单的 XML Schema 校验往往能发现字段类型错误。5. 自动化落地与工程化经验让 SBOM 真正融入研发流程5.1 在 CI/CD 流程中集成 SBOM 生成如果你已经习惯用 GitHub Actions 或 GitLab CI把 SBOM 生成集成进去是水到渠成的事。下面这段 GitHub Actions 的片段是我在一个前后端分离项目中实际用过的name: Generate SBOM on: push: branches: [main, release/**] jobs: cyclonedx: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Set up JDK uses: actions/setup-javav4 with: java-version: 17 distribution: temurin - name: Generate CycloneDX SBOM run: mvn org.cyclonedx:cyclonedx-maven-plugin:2.7.11:makeAggregateBom - name: Upload SBOM artifact uses: actions/upload-artifactv4 with: name: bom path: target/bom.json对于非 Java 项目只需要替换生成命令流程骨架完全一致。核心逻辑是“构建成功后生成 SBOM并作为构建产物导出”。5.2 用 SBOM 做发布准入的实战设置曾经在跟团队讨论“要不要把 SBOM 作为发布准入条件”时有同事提过反对意见“这会拖慢发布速度。”实际上生成 SBOM 的时间开销远小于一次镜像构建以 Maven 为例大约增加 10 到 20 秒Node 更是在 5 秒左右几乎不会影响发布效率。我们最终在发布流水线里加了一个检查脚本逻辑很简单verify_sbom_exist() { if [ ! -f $1 ]; then echo SBOM not found. Build failed. exit 1 fi # 可继续加入格式校验、漏洞扫描阻断等逻辑 }把这个脚本挂在制品归档前一旦 SBOM 缺失或生成失败构建直接中断。这套“硬门禁”看起来很笨但它让“没有 SBOM 的构建无法发布”这个原则在执行层面变得不可绕过。5.3 SBOM 存储与版本管理的注意事项SBOM 文件本身也是一种需要版本管理的资产。我建议把 SBOM 和制品文件放同一个版本目录下例如myapp/1.0.0/myapp.jar和myapp/1.0.0/myapp.bom.json。文件名建议带上应用名和版本号例如myapp-1.0.0-cyclonedx.sbom.json防止不同版本的 SBOM 互相覆盖。对于需要应对审计的场景建议额外保留一份 PDF 或人类可读格式的摘要视图毕竟有些审计人员不想直接看 JSON。5.4 SBOM 生成和漏洞扫描的关系别再搞混了最后再强调一遍SBOM 生成 ≠ 漏洞扫描。SBOM 只是一张“成分表”它不会告诉你哪些组件有漏洞。漏洞扫描是基于 SBOM 中的组件信息去匹配漏洞库。有些工具能一步到位比如 Trivy 的扫描功能能自动生成 SBOM 并扫描但你要清楚每一步发生在什么时候。一个比较规范的流水线是这样的构建产物 → 生成 SBOM → 基于 SBOM 做漏洞扫描高风险阻断 → 归档 SBOM → 发布。这样既能保证发布效率又能把安全信息沉淀下来。比如我们实际跑过的一条命令用来基于 SBOM 文件的 CycloneDX 格式直接评估grype sbom:./bom.cdx.json --fail-on high --output table一旦扫描出高危组件流水线失败发布被阻断。6. 写在最后的经验总结从第一次接触 SBOM 的懵懂到如今把 SBOM 嵌入多个项目的发布流水线我最深的体会是SBOM 的价值不在那张“清单文件”本身而在于它把软件的组成信息变成了可供自动化消费的数据。你可以用同一份 CycloneDX JSON 去匹配漏洞库、提取许可证、做变更对比也可以让它成为对外交付时展示透明度的凭证。无论是面对客户的安全评审还是自己团队内部的供应链梳理SBOM 都是一项值得尽早补齐的基础能力。如果在做的过程中让我只保留两条建议我会说第一选一个和你的技术栈匹配的工具先跑通“生成 → 校验 → 存档”这条最小链路第二别满足于“能生成”要尽快把它接进发布流水线、联动漏洞扫描让 SBOM 从一份“文档”变成一个持续运转的流程。另外一个亲测有效的经验是在项目早期就把 SBOM 生成接入 CI比项目成熟后再补轻松十倍。真等到外部审计来了再补所有依赖、许可证、传递依赖都散落在各个开发者本地环境里补起来极其痛苦。早做成本低得多。