Commit e8c54ffb3d ("utils/generate-cyclonedx: generate vcs
externalReferences for source repos") added externalReferences to the source
code of packages.
This unfortunately causes issues with packages (in br2-external) fetching
from git using the scp-like syntax, E.G.:
FOO_SITE_METHOD = git
FOO_SITE = git@github.com:<project>/<repo>.git
Which ends up in the SBOM as:
[
{
"type": "vcs",
"url": "git@github.com:<project>/<repo>.git",
"comment": "git repository"
}
]
This (correctly) causes Dependency track to reject the SBOM import with:
{
"status": 400,
"title": "The uploaded BOM is invalid",
"detail": "Schema validation failed",
"errors": [
"$.components[2].externalReferences[0].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference",
"$.components[2].externalReferences[0].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference",
"$.components[2].externalReferences[0].url: does not match the regex pattern ^urn:cdx:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/[1-9][0-9]*$",
]
}
The CycloneDX spec indeed requires a URI:
The URI (URL or URN) to the external reference. External references are
URIs and therefore can accept any URL scheme including https (RFC-7230),
mailto (RFC-2368), tel (RFC-3966), and dns (RFC-4501)
https://cyclonedx.org/docs/1.6/json/#metadata_tools_oneOf_i0_components_items_externalReferences_items_url
The user@host:project/repo.git is a git-specific shorthand for a git-over-ssh URL. From man git-clone:
Git supports ssh, git, http, and https protocols (in addition, ftp and ftps
can be used for fetching, but this is inefficient and deprecated; do not use
them).
The native transport (i.e. git:// URL) does no authentication and should
be used with caution on unsecured networks.
The following syntaxes may be used with them:
• ssh://[user@]host.xz[:port]/path/to/repo.git/
• git://host.xz[:port]/path/to/repo.git/
• http[s]://host.xz[:port]/path/to/repo.git/
• ftp[s]://host.xz[:port]/path/to/repo.git/
An alternative scp-like syntax may also be used with the ssh protocol:
• [user@]host.xz:path/to/repo.git/
So convert the scp-like syntax to ssh:// URLs in parse_uris() for spec
compliance.
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
Acked-By: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
The 'show-info' output always the location relative to the Buildroot
root directory.
When running 'generate-cyclonedx' outside of the Buildroot root
directory the hash file locations are not correct. Only using the
'hash_file' variable coming from 'show-info' doesn't guarantee the
script to succeed.
Use the 'brpath' variable to correctly reference the absolute path.
Fixes: 1791b79422 utils/generate-cyclonedx: add hashes from .hash files to externalReferences
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Arnout Vandecappelle <arnout@rnout.be>
Since last commit, utils/generate-cyclonedx can now be used with
Buildroot host tools, provided the user either externally install the
SPDX schema in the DL_DIR themselves or have
BR2_PACKAGE_HOST_PYTHON3_SSL and BR2_PACKAGE_HOST_CA_CERTIFICATES both
enabled. If the latter is missing, Python will complain with:
Traceback (most recent call last):
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 1321, in do_open
h.request(req.get_method(), req.selector, req.data, headers,
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
encode_chunked=req.has_header('Transfer-encoding'))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/http/client.py", line 1358, in request
self._send_request(method, url, body, headers, encode_chunked)
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/http/client.py", line 1404, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/http/client.py", line 1353, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/http/client.py", line 1113, in _send_output
self.send(msg)
~~~~~~~~~^^^^^
File "/buildroot/build/host/lib/python3.14/http/client.py", line 1057, in send
self.connect()
~~~~~~~~~~~~^^
File "/buildroot/build/host/lib/python3.14/http/client.py", line 1499, in connect
self.sock = self._context.wrap_socket(self.sock,
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
server_hostname=server_hostname)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/ssl.py", line 455, in wrap_socket
return self.sslsocket_class._create(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
sock=sock,
^^^^^^^^^^
...<5 lines>...
session=session
^^^^^^^^^^^^^^^
)
^
File "/buildroot/build/host/lib/python3.14/ssl.py", line 1076, in _create
self.do_handshake()
~~~~~~~~~~~~~~~~~^^
File "/buildroot/build/host/lib/python3.14/ssl.py", line 1372, in do_handshake
self._sslobj.do_handshake()
~~~~~~~~~~~~~~~~~~~~~~~~~^^
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1081)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/buildroot/utils/generate-cyclonedx", line 53, in <module>
urllib.request.urlretrieve(SPDX_SCHEMA_URL, SPDX_SCHEMA_PATH)
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 212, in urlretrieve
with contextlib.closing(urlopen(url, data)) as fp:
~~~~~~~^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 187, in urlopen
return opener.open(url, data, timeout)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 487, in open
response = self._open(req, data)
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 504, in _open
result = self._call_chain(self.handle_open, protocol, protocol +
'_open', req)
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 464, in _call_chain
result = func(*args)
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 1369, in https_open
return self.do_open(http.client.HTTPSConnection, req,
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
context=self._context)
^^^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 1324, in do_open
raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1081)>
Provide some hint as to what could be missing from Buildroot host.
Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
Signed-off-by: Arnout Vandecappelle <arnout@rnout.be>
urllib will eventually complain that it does not know what https is:
Traceback (most recent call last):
File "/buildroot/utils/generate-cyclonedx", line 53, in <module>
urllib.request.urlretrieve(SPDX_SCHEMA_URL, SPDX_SCHEMA_PATH)
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 212, in urlretrieve
with contextlib.closing(urlopen(url, data)) as fp:
~~~~~~~^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 187, in urlopen
return opener.open(url, data, timeout)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 487, in open
response = self._open(req, data)
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 509, in _open
return self._call_chain(self.handle_open, 'unknown',
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
'unknown_open', req)
^^^^^^^^^^^^^^^^^^^^
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 464, in _call_chain
result = func(*args)
File "/buildroot/build/host/lib/python3.14/urllib/request.py", line 1397, in unknown_open
raise URLError('unknown url type: %s' % type)
urllib.error.URLError: <urlopen error unknown url type: https>
It's quite confusing really but what it means is that host Python was
built without ssl support.
This adds an additional check importing _ssl and if the module is not
found, tell the user what to do. This still doesn't make Buildroot's
host Python3 fully usable for utils/generate-cyclonedx as it'll then
fail when trying to validate certificates, this will be handled in the
next few commits.
Note that ssl Python library is always built in CPython, but itself
imports the _ssl module which is the one that may be disabled with
py_cv_module__ssl=n/a (which is what happens when
BR2_PACKAGE_HOST_PYTHON3_SSL is not enabled), hence why _ssl is checked
and not ssl.
Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
Signed-off-by: Arnout Vandecappelle <arnout@rnout.be>
Buildroot-local patches can only be suffixed by .patch, otherwise they
either need to be downloaded via <PKG>_PATCH or manually applied via
$(APPLY_PATCHES) in a <PKG>_{PRE,POST}_PATCH_HOOKS like in
linux/linux.mk. In the former case, they are then listed in the
show-info output with a full URL (prefixed by '<PKG>_SITE_METHOD+'). In
the latter case, they not listed as patches at the moment, just as
externalReferences.
By removing "support" for those compressed patches, we can avoid the bz2
dependency and can now use Buildroot's host-python3 package without
BR2_PACKAGE_HOST_PYTHON3_BZIP2 to run utils/generate-cyclonedx.
Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
Signed-off-by: Arnout Vandecappelle <arnout@rnout.be>
Some packages do not have a http/https download URL for a source tarball,
but are acquired over a version control system like git. If so, add
externalReferences of type "vcs" for such URLs.
As most git repositories use a https:// transport that may not indicated the
repository type, add a "comment" due to the lack of a better mechanism in
CycloneDX.
While the hashes are calculated over a tarball created locally, it still may
be useful, so add them for "vcs" externalReferences as well.
Signed-off-by: Martin Willi <martin@strongswan.org>
Acked-By: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Arnout Vandecappelle <arnout@rnout.be>
Commit dc4af8bfa9 ("utils/generate-cyclonedx: use direct dependencies")
removes indirect dependencies from any listed component, as required by
CycloneDX. The root component, however, still includes indirect dependencies,
as it just takes the components from the show-info output.
Fix this by collecting all component dependencies, and then filter the root
component dependencies to include direct dependencies only.
Signed-off-by: Martin Willi <martin@strongswan.org>
Acked-By: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Arnout Vandecappelle <arnout@rnout.be>
Add 'source' attribute to each CVE in vulnerabilities node, including NVD
URL reference to enable proper import into Dependency-Track.
Dependency-Track's VEX importer requires the source attribute to
properly process vulnerability entries. Without it, vulnerabilities are
skipped during import with "does not have an ID and / or source" warnings.
Include the full NVD URL following the CycloneDX 1.6 documentation format:
https://nvd.nist.gov/vuln/detail/{CVE-ID}
Test Environment:
- Buildroot: 2025.02.11 (or master)
- Dependency-Track: v4.13.6
Test Results - BEFORE (without source attribute):
apiserver_1 | 2026-02-23 16:05:40,890 INFO [VexUploadProcessingTask] Processing CycloneDX VEX uploaded to project: e43fe185-c0a3-4e3a-a908-667344a66a9c
apiserver_1 | 2026-02-23 16:05:40,941 WARN [CycloneDXVexImporter] VEX vulnerability at position #0 does not have an ID and / or source; Skipping it
apiserver_1 | 2026-02-23 16:05:40,941 WARN [CycloneDXVexImporter] VEX vulnerability at position #1 does not have an ID and / or source; Skipping it
...
apiserver_1 | 2026-02-23 16:05:40,941 WARN [CycloneDXVexImporter] VEX vulnerability at position #19 does not have an ID and / or source; Skipping it
apiserver_1 | 2026-02-23 16:05:40,941 INFO [CycloneDXVexImporter] The uploaded VEX does not contain any applicable vulnerabilities; Skipping VEX import
Test Results - AFTER (with source):
apiserver_1 | 2026-02-23 16:17:13,492 INFO [VexUploadProcessingTask] Processing CycloneDX VEX uploaded to project: e43fe185-c0a3-4e3a-a908-667344a66a9c
apiserver_1 | 2026-02-23 16:17:14,054 INFO [VexUploadProcessingTask] Completed processing of CycloneDX VEX for project: e43fe185-c0a3-4e3a-a908-667344a66a9c
CVEs are correctly imported in Dependency-Track
Signed-off-by: Fabien Lehoussel <fabien.lehoussel@smile.fr>
Acked-By: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Romain Naour <romain.naour@smile.fr>
Since its introduction in [1], by default the `generate-cyclonedx`
script doesn't include buildroot's virtual packages in its 'components'
list, unless using the `--virtual` argument.
References to virtual packages present in the 'show-info' output are
filtered out in the resulting dependencies.
This patch fix the default CycloneDX dependencies generation
without virtual packages to reference the packages that provide the
virtual package instead of just dropping the virtual package itself.
If we use the package `lbase64` that depends on the virtual package
`luainterpreter` as an example. The 'dependency' entry looks like the
following:
```
{
"ref": "lbase64",
"dependsOn": [
"host-skeleton",
"skeleton-init-common",
"skeleton-init-sysv",
"toolchain-external-bootlin"
]
}
```
The `luainterpreter` dependency is missing.
After applying this patch, package that provides the `luainterpreter` is
present:
```
{
"ref": "lbase64",
"dependsOn": [
"host-skeleton",
"lua",
"skeleton-custom",
"skeleton-init-sysv"
]
}
```
In the case of a virtual package provided by multiple packages all those
packages will be listed. This happens when generating an SBOM on the
entire Buildroot packages.
[1] dbab39e2d9 support/scripts/generate-cyclonedx.py: add script to generate CycloneDX-style SBOM
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
Since the introduction of the `generate-cyclonedx` script in [1] the
dependencies were 'recursive'. This means that the dependencies of a
package dependency were included.
The CycloneDX spec [2] states that only direct dependencies needs to be
included.
This patch drop the recursive dependencies.
[1] dbab39e2d9 support/scripts/generate-cyclonedx.py: add script to generate CycloneDX-style SBOM
[2] https://cyclonedx.org/docs/1.6/json/#dependencies
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
The definition of the project name & version is stored under the
`metadata:component` CycloneDX property.
Since the introduction of the `generate-cyclonedx` script [1] a
'buildroot' dependency entry that depends on every components has been
part of the generated SBOM.
Tools such as 'DependencyTrack' relies on such entry to create graph of
the entire project.
With the commit [2] that introduced the option to pass a custom project
name and version, this dependency reference was not updated to match the
custom 'bom-ref'.
This patch fixes the reference to match the custom project name.
[1] dbab39e2d9 support/scripts/generate-cyclonedx.py: add script to generate CycloneDX-style SBOM
[2] 9cbbc47762 utils/generate-cyclonedx: add project name and version options
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
The CycloneDX specification for vulnerabilities defines four analysis
states ([1]) for cases where a vulnerability does not affect a component:
* resolved
* resolved_with_pedigree
* not_affected
* false_positive
Currently, the metadatas present in Buildroot does not allow an accurate
mapping of ignored CVEs to the appropriate CycloneDX vulnerability
categories. As a result, all ignored CVEs are currently marked as
'in_triage' by default.
This default analysis was established during the introduction of the
'generate-cyclonedx' script. The reasoning at the time was that SBOM
consumers might want to re-evaluate ignored vulnerabilities, as the
Buildroot infrastructure could not reliably determine their actual
state.
This patch adds support for automatically marking vulnerabilities as
'resolved_with_pedigree' when a Buildroot patch includes a 'CVE:''
tag in its header referencing the CVE identifier.
The 'CVE:' tag appears alongside the already required 'Upstream:', if
the patch address a security vulnerability and may be repeated if a
patch addresses multiple vulnerabilities.
If a vulnerability is addressed by multiple patches, each patch will need to
reference the vulnerability identifier.
For details on how CycloneDX handles 'resolved_with_pedigree', see
[1][2].
As an example, the CVE-2025-3198 from the binutils package will result
in the following pedigree for the binutils component:
```
{
"type": "unofficial",
"diff": {
"text": {
"content": "..."
}
},
"resolves": [
{
"type": "security",
"name": "CVE-2025-3198"
}
]
},
```
The `resolves` property is an array of issue the pedigree resolves. If
multiple are addressed by the same patch, then multiple identifier will be
present in this array.
In the listed vulnerabilities the entry for the CVE-2025-3198 looks like
this:
```
{
"id": "CVE-2025-3198",
"analysis": {
"state": "resolved_with_pedigree",
"detail": "The CVE 'CVE-2025-3198' has been marked as ignored by Buildroot"
},
"affects": [
{
"ref": "binutils"
}
]
}
```
[1] https://cyclonedx.org/docs/1.6/json/#vulnerabilities_items_analysis_state
[2] https://cyclonedx.org/docs/1.6/json/#components_items_pedigree_patches_items_resolves
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
The SBOM is easier to read if the dependencies are sorted alphabetically.
Signed-off-by: Fabien Lehoussel <fabien.lehoussel@smile.fr>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
Add options to customize the project name and version in the generated SBOM
metadata and set buildroot generate-cyclonedx as a tool in the metadata
section.
Signed-off-by: Fabien Lehoussel <fabien.lehoussel@smile.fr>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
This makes it more readable and easier to quickly identify basic information.
Signed-off-by: Fabien Lehoussel <fabien.lehoussel@smile.fr>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
By default, use an indent of 2, which is the same as what 'jq' uses.
This omits the need for 'jq' in the example usage. Also, add a new
line to the output while at it.
Signed-off-by: Thomas Devoogdt <thomas.devoogdt@barco.com>
Signed-off-by: Julien Olivain <ju.o@free.fr>
The real file is utils/generate-cyclonedx,
not utils/generate-cyclonedx.py.
Signed-off-by: Thomas Devoogdt <thomas.devoogdt@barco.com>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
Check if the script shall read from stdin if data is piped into.
Otherwise read from the input file or if not specified print usage and
exit.
Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
There is a growing need to generate software bill of materials (SBOM) from
buildroot configurations. Right now there are different solutions available
for buildroot users `show-info`, `legal-info` and `pkg-stats`.
They all generate similar information but in a format that is specific
to buildroot.
CycloneDX is a SBOM specification that can be consumed by different services.
This patch introduces a Python script, that converts the JSON output of the
show-info Makefile target to a CycloneDX-style SBOM.
The script output contains the following information.
- A list of all packages, or "components" with information about
version, cpe (if available), applied patches.
- By default virtual packages are not listed in the SBOM.
- Additional information is added to the component 'properties' to
specify wheter the component is present on the target or the host
under the `BR_TYPE` property name.
- An overview of the licenses applicable to each package. If possible,
the names of these licenses have been matched to known SPDX license
identifiers.
- Per package, a list of (recursive) dependencies on other packages.
- A list of ignored CVE and their associated component.
More information on CycloneDX at https://cyclonedx.org/.
Usage:
make show-info | utils/generate-cyclonedx.py | jq '.'
Example output:
```
{
"bomFormat": "CycloneDX",
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
"specVersion": "1.6",
"components": [
{
"bom-ref": "busybox",
"type": "library",
"name": "busybox",
"version": "1.36.1",
"licenses": [
{
"license": {
"id": "GPL-2.0"
}
},
...
],
"cpe": "cpe:2.3:a:busybox:busybox:1.36.1:*:*:*:*:*:*:*",
"pedigree": {
"patches": [
{
"type": "unofficial",
"diff": {
"text": {
"content": "..."
}
}
}
]
}
"properties": [
{
"name": "BR_TYPE",
"value": "target"
}
]
},
...
],
"dependencies": [
{
"ref": "busybox",
"dependsOn": [
"host-skeleton",
"skeleton",
"skeleton-init-sysv",
"skeleton-init-common",
...
}
...
],
"vulnerabilities": [
{
"id": "CVE-2022-28391",
"analysis": {
"state": "in_triage",
"detail": "The CVE 'CVE-2022-28391' has been marked as ignored by Buildroot"
},
"affects": [
{
"ref": "busybox"
}
]
},
...
],
"metadata": {
"component": {
"bom-ref": "buildroot",
"name": "buildroot",
"type": "firmware",
"version": "2024.02-4744-gafea667f00-dirty"
}
}
}
```
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Co-authored-by: Matthias Swiggers <matthias.swiggers@mind.be>
Reviewed-by: Vincent Jardin <vjardin@free.fr>
[Arnout:
- alphabetically order imports;
- use endswith instead if 'in' to check suffix;
- add usage to help text;
- remove .py suffix.
]
Signed-off-by: Arnout Vandecappelle <arnout@mind.be>