Introduction
An SBOM (Software Bill of Materials) provides a detailed inventory of software components included in an artifact. SBOMs allow developers to improve and analyze the software supply chain security profile for their Swift projects (for example, determining whether a dependency that's being used has a vulnerability). Also, some companies, governments, and other regulatory bodies require SBOMs to be produced for auditing purposes.
There are two common formats for SBOMs: CycloneDX and SPDX.
Motivation
Currently, Swift Package Manager lacks built-in support for generating SBOMs. Instead, developers have to rely on external or third-party tools to create SBOMs. External and third-party tools usually analyze the Package.swift and Package.resolved files for package dependencies, so information about what product uses which dependencies is missing.
Proposed Solution
This pitch describes adding CycloneDX and SPDX SBOM generation capabilities to Swift Package Manager as part of the build command and as a separate package subcommand.
Integrated Build Command
swift build will take an optional flag --sbom-spec that triggers CycloneDX and/or SPDX SBOM generation as part of the build command. SwiftPM will analyze the resolved package graph and optionally the SwiftBuild build system’s dependency graph, and produce SBOMs for the root package or product.
Re-running the command will not overwrite previously generated SBOMs; timestamps will be appended to SBOMs. This is to address the case when the same product or package is intentionally built multiple times with different flags; all SBOMs (not just the most recent) are relevant to the user.
If SBOM generation fails, the build will return an error.
Traits and Conditions
Dependencies affected by traits are taken into consideration during package resolution. SwiftPM’s resolved package graph will already reflect the impact of traits on dependencies.
Conditions (e.g., OS-specific dependencies) are evaluated at build-time. SwiftPM’s package graph does not reflect conditions. Instead, the SBOM command will look at SwiftBuild build system’s computed dependency graph to determine which dependencies from the package graph should be included or excluded in the final SBOMs.
Incremental Builds
SBOM generation is not incremental, so if SBOM generation is specified, the build will always produce new SBOMs, even if the artifacts have not changed since the last build.
SBOM generation does not affect whether an artifact build will be full or incremental.
CLI Examples
A user can run the following commands:
# To generate CycloneDX SBOM file for a package using both package graph and build graph
$ swift build --build-system swiftbuild --sbom-spec cyclonedx
# To generate SPDX SBOM file for a package using both package graph and build graph
$ swift build --build-system swiftbuild --sbom-spec spdx
# To generate CycloneDX and SPDX SBOM files for a package using only the package graph
$ swift build --sbom-spec cyclonedx --sbom-spec spdx
# To generate a CycloneDX or SPDX SBOM file for a single product using both package graph and build graph
$ swift build --build-system swiftbuild --product MyProduct1 --sbom-spec cyclonedx
$ swift build --build-system swiftbuild --product MyProduct2 --sbom-spec spdx
# To generate SBOM files into a specific directory
$ swift build --build-system swiftbuild --sbom-spec cyclonedx --sbom-spec spdx --sbom-dir /MyDirectory
Optional Flags
--sbom-spec will trigger SBOM generation when a build is run. Either cyclonedx or spdx or both can be passed to indicate what kind of SBOMs to generate.
--sbom-spec can only be used with one of the following:
- without
--productand without--targetflags (i.e., a SBOM for the entire package), or - with
--productflag (i.e., a SBOM for a specific product)
The build will error if --sbom-spec is used with the --target flag.
If --build-system swiftbuild is not specified, a warning will be emitted that only the package graph is being used for SBOM generation. (The build dependency graph is only available through SwiftBuild.)
cyclonedx and spdx flags will always point to the most recently SwiftPM-supported major versions, but users have the option to specify the major version they'd like.
OPTIONS:
--sbom-spec <spec> Set the SBOM specification.
cyclonedx - Most recent major version of CycloneDX supported by SwiftPM (currently: 1.7)
spdx - Most recent major version of SPDX supported by SwiftPM (currently: 3.0.1)
cyclonedx1 - Most recent minor version of CycloneDX v1 supported by SwiftPM (currently: 1.7)
spdx3 - Most recent minor version of SPDX v3 supported by SwiftPM (currently: 3.0.1)
# Future: cyclonedx2...
# Future: spdx4...
Additionally, users can specify the following optional flags (which must appear with --sbom-spec):
--sbom-dir <sbom-dir> The absolute or relative directory path to generate the SBOM(s) in.
--sbom-filter <filter> Filter the SBOM components and dependencies by products and/or packages.
all - Include all entities in the SBOM (default)
package - Only include package information and package dependencies
product - Only include product information and product dependencies
Configuration
Environment variables can be used for SBOM generation configuration in the short-term.
An issue in Github will be raised to address changing SBOM environment variables to a configuration file.
SWIFTPM_BUILD_SBOM_DIRECTORY: specifies which directory the SBOMs should be generated in. If--sbom-diris passed to swift build,--sbom-dirwill take precedence.SWIFTPM_BUILD_SBOM_FILTER: specifies which filter to apply, defaults to all. If--sbom-filteris passed toswift build,--sbom-filterwill take precedence.- (Needs further discussion)
SWIFTPM_BUILD_SBOM_WITHOUT_FAILURE: automatically generates both CycloneDX and SPDX SBOMs as part of the swift build command. If--sbom-specis passed to swift build,--sbom-specwill take precedence. A failure in SBOM generation will not cause the build to fail.- This addresses the use case where an external party (e.g., infrastructure or security teams) wants to generate SBOMs for all Swift projects under their purview without interfering with build results.
- Alternatives include:
- A specific exit code to use when the build passes but SBOM generation fails (e.g., permission to write to SBOM folder is denied). However, this would cause most CI pipelines to still fail, unless an exception is built into the CI system, so this is not ideal for most infrastructure and security teams.
- An additional environment variable
SWIFTPM_BUILD_SBOM_WITH_FAILUREthat automatically generates CycloneDX and SPDX SBOMs as part of theswift buildcommand. A failure in SBOM generation will cause the build to fail (i.e., default behavior, but without changing theswift buildcommand that teams have written).- For example, if a team is using the
--targetflag, the build will pass, but the build exit code will be a failure code because SBOM generation does not work with--target.
- For example, if a team is using the
- Two environment variables,
SWIFTPM_BUILD_SBOM(default:false) andSWIFTPM_SBOM_RECORD_FAILURE(default:true) that toggle whether to build SBOMs automatically and whether to fail the build if SBOM generation fails. This would also necessitate adding a--sbom-record-failureflag to the CLI, so that CLI and environment variables match.
Package Subcommand
SBOM generation will also be added as a separate subcommand swift package generate-sbom. It will share the same SBOM flags as SBOM generation for swift build. Unlike SBOM generation in swift build, swift package generate-sbom will not call the build or use the SwiftBuild build dependency graph.
This subcommand is to address use cases where an SBOM might need to be created after a build (for example, in a CICD pipeline or using a different version of the toolchain), but calling swift build again is undesirable or impossible.
The subcommand will always emit a warning that the SBOM may not be fully accurate because it only uses the package graph. The user will have the option to pass --disable-automatic-resolution to force SBOM generation to fail if the Package.resolved is not up-to-date.
Note: Using the package subcommand can result in dependency resolution timing issues. There is an edge case where an artifact is built, the dependency graph changes, and then the SBOM is generated. In that case, the SBOM will not accurately reflect the built artifact’s components and dependencies.
CLI Examples
A user can run the following commands:
# To generate CycloneDX SBOM file for a package using the package graph
$ swift package generate-sbom --sbom-spec cyclonedx
# To generate SPDX SBOM file for a package using the package graph
$ swift package generate-sbom --sbom-spec spdx
# To generate CycloneDX and SPDX SBOM files for a package using the package graph
$ swift package generate-sbom --sbom-spec cyclonedx --sbom-spec spdx
# To generate a CycloneDX or SPDX SBOM file for a single product using the package graph
$ swift package generate-sbom --product MyProduct1 --sbom-spec cyclonedx
$ swift package generate-sbom --product MyProduct2 --sbom-spec spdx
# To generate SBOM files into a specific directory
$ swift package generate-sbom --sbom-spec cyclonedx --sbom-spec spdx --sbom-dir /MyDirectory
Design
The SBOM generation will have three layers:
- Extractor: Reads information from the package graph and build dependency graph and saves it into internal data structures
- Converters: Read information from internal data structures and convert them into CycloneDX and SPDX equivalent structures
- Validator: Reads CycloneDX and SPDX SBOM JSON objects and validates them against the appropriate schema
SBOM Content
This feature will support the most recent minor versions of major versions starting with CycloneDX 1 and SPDX 3.
Only JSON files will be generated. JSON is a common file format supported by both CycloneDX and SPDX. JSON is the preferred format for CycloneDX and becoming more common in SPDX.
Generated SBOMs will include:
Components
- Component CycloneDX type or package SPDX purpose; name; and version
- Package URL (PURL) for unique identification
- Source repository information
CycloneDX:
{
"bom-ref" : "swift-asn1:SwiftASN1",
"name" : "SwiftASN1",
"pedigree" : {
"commits" : [
{
"uid" : "40d25bbb2fc5b557a9aa8512210bded327c0f60d",
"url" : "https://github.com/apple/swift-asn1.git"
}
]
},
"properties" : [
{
"name" : "swift-entity",
"value" : "swift-product"
}
],
"purl" : "pkg:swift/github.com/apple/swift-asn1:SwiftASN1@1.5.0",
"scope" : "required",
"type" : "library",
"version" : "1.5.0"
}
SPDX:
{
"creationInfo" : "_:creationInfo",
"description" : "<ResolvedPackage: swift-asn1>",
"externalUrl" : "pkg:swift/github.com/apple/swift-asn1@1.5.0",
"name" : "swift-asn1",
"software_internalVersion" : "1.5.0",
"software_primaryPurpose" : "library",
"spdxId" : "urn:spdx:swift-asn1",
"summary" : "swift-package",
"type" : "software_Package"
},
{
"creationInfo" : "_:creationInfo",
"from" : "urn:spdx:40d25bbb2fc5b557a9aa8512210bded327c0f60d",
"relationshipType" : "generates",
"spdxId" : "urn:spdx:40d25bbb2fc5b557a9aa8512210bded327c0f60d-generates",
"to" : [
"urn:spdx:swift-asn1",
"urn:spdx:swift-asn1:SwiftASN1"
],
"type" : "Relationship"
},
{
"externalIdentifierType" : "gitoid",
"identifier" : "urn:spdx:40d25bbb2fc5b557a9aa8512210bded327c0f60d",
"identifierLocator" : [
"https://github.com/apple/swift-asn1.git"
],
"type" : "ExternalIdentifier"
}
Dependencies
- Direct and transitive relationships
- Package-to-package dependencies (only packages used by the root package or specified product)
- Package-to-product dependencies (i.e., a package depends on its own products)
- Product-to-product dependencies (only products used by the root package or specified product)
CycloneDX:
{
"dependsOn" : [
"swift-asn1:SwiftASN1"
],
"ref" : "swift-crypto:_CryptoExtras"
},
{
"dependsOn" : [
"swift-crypto:Crypto",
"swift-crypto:_CryptoExtras",
"swift-asn1"
],
"ref" : "swift-crypto"
},
{
"dependsOn" : [
"swift-asn1",
"swift-crypto",
"swift-certificates:X509"
],
"ref" : "swift-certificates"
},
{
"dependsOn" : [
"swift-crypto:Crypto",
"swift-asn1:SwiftASN1",
"swift-crypto:_CryptoExtras"
],
"ref" : "swift-certificates:X509"
},
{
"dependsOn" : [
"swift-asn1:SwiftASN1"
],
"ref" : "swift-asn1"
}
SPDX:
{
"creationInfo" : "_:creationInfo",
"from" : "urn:spdx:swift-crypto:_CryptoExtras",
"relationshipType" : "dependsOn",
"spdxId" : "urn:spdx:swift-crypto:_CryptoExtras-dependsOn",
"to" : [
"urn:spdx:swift-asn1:SwiftASN1"
],
"type" : "Relationship"
},
{
"creationInfo" : "_:creationInfo",
"from" : "urn:spdx:swift-crypto",
"relationshipType" : "dependsOn",
"spdxId" : "urn:spdx:swift-crypto-dependsOn",
"to" : [
"urn:spdx:swift-crypto:Crypto",
"urn:spdx:swift-crypto:_CryptoExtras",
"urn:spdx:swift-asn1"
],
"type" : "Relationship"
},
{
"creationInfo" : "_:creationInfo",
"from" : "urn:spdx:swift-certificates",
"relationshipType" : "dependsOn",
"spdxId" : "urn:spdx:swift-certificates-dependsOn",
"to" : [
"urn:spdx:swift-asn1",
"urn:spdx:swift-crypto",
"urn:spdx:swift-certificates:X509"
],
"type" : "Relationship"
},
{
"creationInfo" : "_:creationInfo",
"from" : "urn:spdx:swift-certificates:X509",
"relationshipType" : "dependsOn",
"spdxId" : "urn:spdx:swift-certificates:X509-dependsOn",
"to" : [
"urn:spdx:swift-crypto:Crypto",
"urn:spdx:swift-asn1:SwiftASN1",
"urn:spdx:swift-crypto:_CryptoExtras"
],
"type" : "Relationship"
},
{
"creationInfo" : "_:creationInfo",
"from" : "urn:spdx:swift-asn1",
"relationshipType" : "dependsOn",
"spdxId" : "urn:spdx:swift-asn1-dependsOn",
"to" : [
"urn:spdx:swift-asn1:SwiftASN1"
],
"type" : "Relationship"
}
Metadata
- SwiftPM version used
- SBOM timestamp
- Spec version
CycloneDX:
"metadata" : {
...
"timestamp" : "2025-11-19T21:42:50Z",
"tools" : {
"components" : [
{
"bom-ref" : "urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6",
"licenses" : [
{
"license" : {
"id" : "Apache-2.0",
"url" : "http://swift.org/LICENSE.txt"
}
}
],
"name" : "swift-package-manager",
"purl" : "pkg:swift/github.com/swiftlang/swift-package-manager@6.3.0-dev",
"scope" : "excluded",
"type" : "application",
"version" : "6.3.0-dev"
}
]
}
},
"serialNumber" : "urn:uuid:8318774a-f646-4eab-a08c-0dc3f952abc5",
"specVersion" : "1.7",
"version" : 1
SPDX:
{
"creationInfo" : "urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6:creationInfo",
"from" : "urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6",
"relationshipType" : "hasDeclaredLicense",
"spdxId" : "urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6-hasDeclaredLicense-urn:spdx:Apache-2.0",
"to" : [
"urn:spdx:Apache-2.0"
],
"type" : "Relationship"
},
{
"creationInfo" : "urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6:creationInfo",
"simplelicensing_licenseExpression" : "Apache-2.0",
"spdxId" : "urn:spdx:Apache-2.0",
"type" : "simplelicensing_LicenseExpression"
},
{
"@id" : "urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6:creationInfo",
"created" : "1970-01-01T00:00:00Z",
"createdBy" : [
"urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6"
],
"specVersion" : "6.3.0-dev",
"type" : "CreationInfo"
},
{
"creationInfo" : "urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6:creationInfo",
"name" : "swift-package-manager",
"spdxId" : "urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6",
"type" : "Agent"
},
{
"@id" : "_:creationInfo",
"created" : "2025-11-19T21:42:50Z",
"createdBy" : [
"urn:uuid:40316357-938f-4a8c-9962-e928fbc251a6"
],
"specVersion" : "3.0.1",
"type" : "CreationInfo"
},
{
"creationInfo" : "_:creationInfo",
"profileConformance" : [
"core",
"software"
],
"rootElement" : [
"urn:spdx:swift-package-manager"
],
"spdxId" : "urn:uuid:5aee3149-5395-4a41-a076-2491437026d3",
"type" : "software_Sbom"
}
Primary Component
CycloneDX:
"component" : {
"bom-ref" : "swift-package-manager",
"name" : "swift-package-manager",
"pedigree" : {
"commits" : [
{
"uid" : "37990426e3f1cb4344f39641e634c76130c1fb42",
"url" : "git@github.com:echeng3805/swift-package-manager.git"
}
]
},
"properties" : [
{
"name" : "swift-entity",
"value" : "swift-package"
}
],
"purl" : "pkg:swift/github.com/echeng3805/swift-package-manager@37990426e3f1cb4344f39641e634c76130c1fb42-modified",
"scope" : "required",
"type" : "application",
"version" : "37990426e3f1cb4344f39641e634c76130c1fb42-modified"
}
SPDX:
{
"creationInfo" : "_:creationInfo",
"description" : "<ResolvedPackage: swift-package-manager>",
"externalUrl" : "pkg:swift/github.com/swiftlang/swift-package-manager@37990426e3f1cb4344f39641e634c76130c1fb42-modified",
"name" : "swift-package-manager",
"software_internalVersion" : "37990426e3f1cb4344f39641e634c76130c1fb42-modified",
"software_primaryPurpose" : "application",
"spdxId" : "urn:spdx:swift-package-manager",
"summary" : "swift-package",
"type" : "software_Package"
}
Security
This will strengthen Swift’s supply chain security profile by providing a basic inventory for packages and products.
Impact on Existing Packages
This feature doesn't modify existing SwiftPM functionality, like the build graph or dependency resolution. It adds an optional feature to the build command and a new package subcommand.
Alternatives Considered
Build Plugin
Build plugins are meant to generate auxiliary build artifacts. An SBOM isn’t really a build artifact; it’s metadata about build artifacts.
Command Plugin
Command plugins need to be added to the Package.swift file to be available. Building SBOM generation directly into swift build gives users the feature without any need to change existing Package.swift files or existing configuration.
Future Features
Some future features that can be added include:
- Best attempt at licenses: Automatic license identification from source code (some edge cases make it difficult to exactly accurately determine licenses; examples: multi-license projects depending on whether payment was rendered, multiple licenses in the repository, license of single file vs license of whole project, changes in license verbiage without changing the name of the license, relicensing for different versions of the same project)
--targetflag support: Support more granular SBOMs (per target)- Additional information: Maintainers’ contact information, commit/forking history, additional build metadata (e.g., host, operating system)
- Additional file formats: XML, TV, YAML. XML is a format supported by both specs; however, XML is not the preferred format for either CycloneDX or SPDX.
- Additional spec versions: CycloneDX 2, SPDX 4 when available
- Merged SBOMs: Generate one SBOM for multiple products/targets, merge or link existing SBOM (e.g., from a third-party SDK) into output SBOM
- Independent CycloneDX and SPDX libraries: For parsing CycloneDX/SPDX files, or supporting other fields besides the bare minimum
- Package.resolved generation: Generate a
Package.resolvedfile based on an SBOM in order to reproduce a dependency graph (e.g., for debugging) - SBOM signing: Sign the SBOM cryptographically to link it to an artifact
- Hashes: Add hashes to the SBOM