DRAFT blog-pnpm-sbom
约 1859 字大约 6 分钟
2026-05-11
在现代软件开发中,我们很少从零开始编写所有代码。一个普通的 Node.js 项目,往往会引入数百甚至数千个第三方依赖。这种“站在巨人的肩膀上”的开发模式极大地提升了效率,但也带来了前所未有的黑盒风险:你真的知道你的项目里到底装了些什么吗?
当 Log4j 漏洞席卷全球,或者某个开源库突然更改了其开源协议时,无数企业陷入了恐慌,因为他们甚至无法快速回答一个简单的问题:“我们的系统受影响了吗?”
为了解决这个痛点,SBOM (Software Bill of Materials) 应运而生。
1. 什么是 SBOM?为什么我们需要它?
SBOM(软件物料清单)就像是食品包装袋背后的“配料表”。它是一份机器可读的清单,详细记录了构建软件产品所使用的所有组件、依赖项、版本号、许可证(License)以及它们之间的层级关系。
对于开发者和企业而言,SBOM 的意义主要体现在三个方面:
- 安全合规与漏洞响应:当 0-day 漏洞爆发时,安全团队可以通过扫描 SBOM 瞬间定位到哪些项目、哪些微服务使用了受影响的组件版本,将排查时间从几天缩短到几分钟。
- 法务与开源许可证合规:商业化产品如果误用了带有“传染性”的开源协议(如 GPL),可能会面临被迫开源或巨额索赔的风险。SBOM 能清晰地列出所有组件的 License,帮助法务团队规避风险。
- 架构健康度与供应链优化:通过分析 SBOM,架构师可以发现项目中存在的依赖冗余(同一个包引入了 N 个不同版本)、僵尸依赖以及技术债务。
2. SBOM 的两大主流格式:CycloneDX vs SPDX
目前业界主要存在两种 SBOM 标准格式:
- CycloneDX:由 OWASP(开放全球应用程序安全项目)主导。它专为应用程序安全上下文和供应链分析而设计,原生支持 VEX(漏洞利用交换)扩展。在安全领域,CycloneDX 是目前最主流、生态支持最好的格式。
- SPDX (Software Package Data Exchange):由 Linux 基金会主导。它最初的侧重点是解决知识产权和许可证合规问题,后来在 2.2+ 版本中也加入了更多安全特性。
在实际工程中,我们通常推荐使用 CycloneDX 格式,因为它在安全漏洞扫描工具(如 Trivy, Dependency-Track)中的集成度更高。
3. 如何生成 SBOM?以 NPM / PNPM 为例
NPM 生态
如果你使用的是 npm,生成 SBOM 非常简单。npm CLI 已经内置了生成命令:
npm sbom --sbom-format cyclonedx > sbom.jsonPNPM 生态的演进(重点)
对于使用 PNPM 的 Monorepo 项目,生成 SBOM 的方式经历了一次重要的演进:
在 PNPM 11 之前: PNPM 原生并不支持生成 SBOM。开发者不得不依赖第三方插件,例如使用 @cyclonedx/cyclonedx-npm 配合一些 hack 手段,或者使用 pnpm-lock-export 导出依赖树后再做转换。过程繁琐且容易出错。
在 PNPM 11 及以后: 好消息是,PNPM 终于原生支持了 SBOM 生成!你只需要一行命令:
pnpm sbom --sbom-format cyclonedx > docs/sbom.json⚠️ 避坑指南:为什么我的 SBOM 里没有 License?
在使用 pnpm sbom 时,你可能会发现一个参数 --lockfile-only。顾名思义,它只读取 pnpm-lock.yaml 来生成 SBOM,速度极快。
但是,千万不要在生产环境使用 --lockfile-only!
因为 pnpm-lock.yaml 文件中根本不包含第三方包的 License 信息。如果你使用了这个参数,生成的 SBOM 中所有组件的 licenses 字段都会是空的(或者显示为 NOASSERTION),这将导致你的许可证合规性分析彻底失效。
正确的做法:
- 确保在本地或 CI/CD 环境中先执行了
pnpm install(让依赖包的package.json缓存到本地 store)。 - 执行不带
--lockfile-only的完整命令。
4. 拒绝“读天书”:SBOM 数据的深度分析
执行完命令后,你会得到一个 sbom.json。打开一看,动辄数万行甚至十几万行的 JSON 代码,人类根本无法阅读。
为了让 SBOM 真正发挥价值,我们需要对其进行数据挖掘。基于 CycloneDX 的数据结构,我们可以进行以下几个维度的深度分析:
- 许可证合规性 (License Compliance): 遍历
components[].licenses,统计 MIT, Apache-2.0, ISC 等协议的占比。重点排查那些 License 为Unknown或高风险的组件。 - 依赖健康度 (Dependency Health - Duplicate Versions): 通过聚合
components[].name和version,找出那些“同名不同版本”的包。例如,如果你的项目中同时存在[email protected]和[email protected],这就是典型的依赖冗余,会导致打包体积臃肿。 - 生态系统分布 (Ecosystem Distribution): 解析包名的 Namespace(如
@babel,@vue,或你们公司的私有域@your-company),统计内部私有包与外部开源包的比例,找出提供包最多的核心组织。 - 核心依赖节点 (Critical Dependencies): 解析 SBOM 中的
dependencies节点(依赖关系树)。计算每个组件的入度 (In-Degree),即它被其他组件依赖的次数。入度最高的 Top 20 组件,就是你项目的底层基石,它们的稳定性直接决定了整个系统的稳定性。
5. 最佳实践:生成单文件交互式 Dashboard
为了将上述分析结果直观地展示给团队,我们可以编写一个 Node.js 脚本,将 SBOM JSON 数据注入到一个预先写好的 HTML 模板中,结合 Vue 3 和 ECharts,生成一个纯静态、无需后端的单文件交互式 Dashboard。
核心实现思路
准备 HTML 模板:编写一个包含 Vue 逻辑和 ECharts 图表容器的 HTML 文件。引入生产环境的 CDN(如
vue.global.prod.js)。数据注入:使用 Node.js 的
fs模块读取sbom.json,将其序列化为字符串,并通过正则替换注入到 HTML 模板的window.__SBOM_DATA__变量中。 rm -rf ~/.config/nvim/.git前端解析与渲染:
- 在 Vue 的
setup()中,编写 Computed 属性来实现上一节提到的 4 种数据分析算法。 - 使用 ECharts 渲染“许可证分布环形图”和“生态系统柱状图”。
- 实现一个带有前端分页和实时搜索功能的“所有组件列表”表格。
- 在 Vue 的
最终,你只需要在 package.json 中配置一条脚本:
"scripts": {
"gen:sbom": "pnpm sbom --sbom-format cyclonedx > docs/sbom.json && node scripts/gen-sbom-html.js"
}运行 npm run gen:sbom 后,你将获得一个精美的 sbom-report.html。你可以直接双击在浏览器中打开它,或者将其部署到静态页面托管服务(如 GitHub Pages)上,供整个团队随时查阅。
总结
SBOM 不仅仅是为了应付安全审计的一纸文件,它是我们洞察软件供应链黑盒的“透视镜”。通过合理地生成、解析和可视化 SBOM 数据,我们能够更好地掌控项目的安全合规与架构健康,让现代软件开发真正做到“心中有数”。
