Add CycloneDX exporter
What does this MR do and why?
This MR adds a new exporter to export the dependency list in CycloneDX format. It conforms to the structure described in #524733 (comment 2396325960).
Click to expand requirements
The SBoM will have the following JSON:
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:c13b4efe-dd33-4975-8733-30b845b897e0",
"version": 1,
"metadata": {
"timestamp": "2025-03-01T20:21:30Z",
"tools": [
{
"vendor": "GitLab",
"name": "GitLab dependency list export",
"version": "17.11.0"
}
],
"authors": [
{
"name": "GitLab",
"email": "support@gitlab.com"
}
],
"component": {
"type": "application",
"name": "project-name",
"externalReferences": [
{
"type": "vcs",
"url": "https://gitlab.com/path/to/project"
}
]
}
},
"components": [
{
"name": "CFPropertyList",
"version": "3.0.7",
"purl": "pkg:gem/CFPropertyList@3.0.7",
"type": "library",
"bom-ref": "pkg:gem/CFPropertyList@3.0.7",
"licenses": [
{
"license": {
"name": "MIT"
}
}
]
},
{
"name": "RedCloth",
"version": "4.3.4",
"purl": "pkg:gem/RedCloth@4.3.4",
"type": "library",
"bom-ref": "pkg:gem/RedCloth@4.3.4",
"licenses": [
{
"license": {
"name": "MIT"
}
}
]
},
...
]
}
The specification describes the requirements for each field. Here are my notes on how we will populate each, where applicable.
-
bomFormatwill be hard-coded toCycloneDX. -
specVersionwill be set by the exporter to the relevant specification version. The current iteration will be implemented in CycloneDX 1.6. When we add support for new specification versions, those will come with a new exporter and export_type. -
serialNumberwill be a randomly generated serial number conforming to RFC 4122. -
versionwill be hard-coded to1. This number is supposed to be incremented if modifications are made to a BoM without generating a new serial number. Since we will generate a new serial number for each export, we don't need to worry about version tracking. -
metadata.timestampwill be set to the current time in UTC. For the most accuracy, the timestamp should be set around the same time that thesbom_occurrencesare queried. Since we will be querying the data in batches, there is a risk that the data could be changed by a running pipeline as we are generating the export. This could be solved by using locking so that sbom ingestion and sbom export block each other and cannot happen at the same time. However, I think it's unlikely enough that we can accept the risk for now. -
metadata.tools[].versionwill be set to the current version of the GitLab instance. -
metadata.componentwill use the name and URL of the project in GitLab. -
components[].bom-refwill be set to thepurlif present. Otherwise, it will be set to a randomly generated serial number. - All other
componentsfields directly map to a column on thesbom_occurrencesrecords except for thepurlwhich can be generated from multiple columns.
This MR is part of a stack:
-
➡️ Add CycloneDX exporter (!185516 - merged) (You are here) - Add cylonedx export parameter (!185517 - merged)
- Add dropdown item for cyclonedx export (!185518 - merged)
SQL
Get start ID: https://console.postgres.ai/gitlab/gitlab-production-main/sessions/37681/commands/115131
SELECT
sbom_occurrences.id
FROM
sbom_occurrences
WHERE
sbom_occurrences.project_id = 278964
ORDER BY
sbom_occurrences.id ASC
LIMIT 1;
Get end ID: https://console.postgres.ai/gitlab/gitlab-production-main/sessions/37681/commands/115132
SELECT
sbom_occurrences.id
FROM
sbom_occurrences
WHERE
sbom_occurrences.project_id = 278964 AND
sbom_occurrences.id >= 5899526150
ORDER BY
sbom_occurrences.id ASC
LIMIT 1
OFFSET 1000;
Get batch: https://console.postgres.ai/gitlab/gitlab-production-main/sessions/37681/commands/115133
SELECT
sbom_occurrences.*
FROM
sbom_occurrences
WHERE
sbom_occurrences.project_id = 278964 AND
sbom_occurrences.id >= 5899526150 AND
sbom_occurrences.id < 5899527683;
References
Screenshots or screen recordings
| Before | After |
|---|---|
How to set up and validate locally
-
Set up the dependency list on a project
-
Run this code in the Rails console:
project = Project.find(<project_id>) export_stub = Struct.new(:project, :author).new(project, User.first) content = Sbom::Exporters::Cyclonedx::V16Service.new(export_stub, project.sbom_occurrences).generate pp Gitlab::Json.parse(content)
Sample output: dependency-list.cdx.json
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.