2. SBOM标准与格式解析
SBOM不是一种“文件后缀”,而是一套用标准语法描述“软件由哪些组件构成、这些组件从哪来、以什么关系组合在一起”的数据模型。工程实践中真正影响工具链兼容性的,是三个主流标准:SPDX、CycloneDX 和 SWID。它们都能表达组件清单,但设计初衷、结构重心和适用场景差异明显。理解这些差异,才能在做 SBOM 生成、交换、入库和风险比对时选对格式,而不是被工具默认值牵着走。
三个标准的设计重心
SPDX(Software Package Data Exchange)由 Linux 基金会主导,最初面向许可证合规与供应链信息交换,ISO/IEC 5962:2021 是其正式国际标准。它的核心是“包(Package)—文件(File)—片段(Snippet)”三层结构,外加关系(Relationship)把对象连成图。SPDX 对许可证、版权、来源信息的表达非常细致,适合法务与合规审计场景。
CycloneDX由 OWASP 社区推动,最初为应用安全与组件分析设计,后来扩展到硬件、服务和机器学习模型。它的核心是“组件(component)—依赖(dependencies)—漏洞(vulnerabilities)”模型,天然贴近漏洞管理和风险治理,字段更紧凑,JSON 表达直观,适合 CI/CD 流水线中高频生成与消费。
SWID(Software Identification Tag)是 ISO/IEC 19770-2 定义的软件标识标签,本质是“给一个已安装软件打标签”,强调软件身份、版本、安装位置和授权状态。它更像资产清单标签,而不是完整的依赖关系图,常用于终端资产管理和软件授权核查。
字段语义:同名不同义,必须逐字段核对
三个标准都包含“名称、版本、供应方、唯一标识”这类字段,但语义并不等价。以唯一标识为例:SPDX 用 SPDXID(如 SPDXRef-Package-log4j-core-2.14.1),CycloneDX 用 bom-ref,SWID 用 tagId。它们都要求在同一文档内唯一,但 SPDXID 还承担关系图的节点引用职责,bom-ref 则被依赖数组和漏洞数组引用。跨格式转换时,如果只做字段名映射而不重建引用关系,依赖图就会断裂。
再看“供应方”字段。SPDX 的 PackageSupplier 区分 Person:、Organization:、NOASSERTION 三种前缀;CycloneDX 的 supplier 是结构化对象,含 name 和 url 数组;SWID 则通过 Entity 元素的 role 属性区分 tagCreator、softwareCreator、licensor。风险治理中判断“这个组件是谁提供的”时,必须明确取的是哪个角色,否则会把标签创建者误当成软件供应方。
版本字段同样有坑。SPDX 的 PackageVersion 是自由文本,CycloneDX 的 version 也是字符串,但 CycloneDX 额外支持 purl(Package URL)作为规范化坐标,例如 pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1。purl 能消除“2.14.1”与“2.14.1-RELEASE”这类写法差异,是跨库比对的关键。SPDX 2.3 起也支持 ExternalRef 携带 purl,但并非所有生成器都会填。
版本差异:升级不是换数字,而是换能力
SPDX 从 2.2 到 2.3 增加了 PrimaryPackagePurpose、PackageVersion 的规范化建议,并强化了 ExternalRef 的 purl 支持;SPDX 3.0 则重构为模块化模型(如 Core、Software、Security 等 Profile),对象类型从 Package/File 扩展为 Element,关系表达更灵活,但工具生态仍在跟进。CycloneDX 1.4 引入 vulnerabilities 数组和 compositions,1.5 增加 formulation、annotations 和更细的 lifecycles,1.6 进一步支持 cryptoProperties 与 declarations。SWID 的 2015 版与 2009 版差异主要在命名空间和 Entity 角色扩展。
版本选择的核心判断是:消费端工具支持到哪个版本,就生成哪个版本。生成端盲目追新,会导致下游漏洞扫描器解析失败;生成端过于保守,则拿不到漏洞数组、生命周期等关键字段。建议在流水线中固定一个“基线版本”,例如 CycloneDX 1.5 JSON,并在 SBOM 元数据中显式记录 specVersion。
选型依据与实操建议
选型不是“哪个标准更好”,而是“哪个标准更匹配当前治理目标”。如果组织同时需要许可证合规和漏洞管理,可以采用“双格式”策略:以 CycloneDX 作为流水线内主格式,用于依赖分析和漏洞比对;在对外交付或法务审计时,用工具转换为 SPDX。转换时重点检查三件事:唯一标识是否重建、依赖关系是否保留、许可证字段是否丢失。
下面给出一个 CycloneDX 1.5 JSON 的最小可用片段,展示组件、purl 与依赖关系如何组织。示例中的组件与版本为教学假设,不代表真实项目依赖。
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"timestamp": "2025-01-15T08:30:00Z",
"component": {
"type": "application",
"bom-ref": "pkg:maven/com.example/demo-app@1.0.0",
"name": "demo-app",
"version": "1.0.0"
}
},
"components": [
{
"type": "library",
"bom-ref": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
"name": "log4j-core",
"version": "2.14.1",
"purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
"supplier": { "name": "Apache Software Foundation" }
}
],
"dependencies": [
{
"ref": "pkg:maven/com.example/demo-app@1.0.0",
"dependsOn": [
"pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1"
]
}
]
}
解析这份 SBOM 时,漏洞扫描器会先取 components[].purl 去漏洞库匹配,再沿 dependencies 判断该组件是否被应用直接或间接引入。如果 bom-ref 与 purl 不一致,或 dependsOn 中引用了不存在的 ref,扫描器可能报“孤立组件”或直接跳过依赖路径。因此,生成后应做一次结构校验:所有 dependsOn 的 ref 必须能在 components[].bom-ref 或 metadata.component.bom-ref 中找到。
最后强调一个工程习惯:无论选 SPDX 还是 CycloneDX,都应在 SBOM 中保留 purl 或等效的规范化坐标。名称和版本是给人看的,purl 是给机器比对用的。风险治理的准确性,往往就取决于这一串坐标是否规范。