Skip to content

fix: CycloneDX SBOM with externalReferences that are not related to CPEs, generate new #5827

Description

@QSchulz

Description

Buildroot recently added externalReferences with the location of source code and hashes of tarballs to components in the CycloneDX SBOM they generate. This now makes cve-bin-tool report CVEs for totally unrelated pieces of software.

To reproduce

Steps to reproduce the behaviour:

  1. Create sbom.cdx.json file with:
{
  "bomFormat": "CycloneDX",
  "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
  "specVersion": "1.6",
  "components": [
    {
      "bom-ref": "html",
      "type": "library",
      "name": "html",
      "version": "v1.0",
      "licenses": [
        {
          "license": {
            "id": "BSD-3-Clause"
          }
        }
      ],
      "externalReferences": [
        {
          "type": "vcs",
          "url": "https://example.com/tarball-v1.0.tar.gz",
          "comment": "git repository",
          "hashes": [
            {
              "alg": "SHA-256",
              "content": "06b35a3596b8417bf66f7bfdd7acd181bcb53fc36dc13216227b5cae154e0219"
            }
          ]
        }
      ]
    }
  ]
}
  1. Run cve-bin-tool --sbom cyclonedx --sbom-file sbom.cdx.json
    Expected behaviour:
    No CVE reported for components with externalReferences that do not contain CPE/pURL.
    Actual behaviour:
    CVEs from unrelated projects being reported based on package name and version. Here our custom package is named html and matches unknown:html and go:html CVEs while without the externalReferences it does not return anything, as there's no CPE information available.

Version/platform info

commit a33ba906b92b0e9d99bc31d94bcecf2a7a0ba474
Linux laptop-q 7.1.3-200.fc44.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Jul  4 19:20:12 UTC 2026 x86_64 GNU/Linux
Python 3.14.6

Anything else?

I think the issue is that

    def parse_ext_ref(self, ext_ref) -> list[tuple[str | None, str | None, str | None]]:
        """
        Parse external references in an SBOM to extract module information.

        Two passes are made through the external references, giving priority to CPE types,
        which will always match the CVE database.

        Args:
        - ext_ref (List[List[str]]): List of lists representing external references.
          Each inner list contains [category, type, locator].

        Returns:
        - Optional[Tuple[str | None, str | None, str | None]]: A tuple containing the vendor, product, and version
          information extracted from the external references, or None if not found.

        """
        decoded = {}
        results = []
        for ref in ext_ref:
            ref_type = ref[1]
            ref_string = ref[2]
            if ref_type == "purl":
                # Validation of purl is performed implicitly within the decode_purl function
                decoded["purl"] = self.decode_purl(ref_string)

            if ref_type == "cpe23Type" and self.is_valid_string("cpe23", ref_string):
                decoded["cpe23Type"] = decode_cpe23(ref_string)

            if ref_type == "cpe22Type" and self.is_valid_string("cpe22", ref_string):
                decoded["cpe22Type"] = decode_cpe22(ref_string)

        # No ext-ref matches, return none
        if decoded.get("purl") is not None:
            LOGGER.debug("Found PURL")
            results.append(decoded.get("purl"))

        if decoded.get("cpe23Type") is not None:
            LOGGER.debug("Found CPE23")
            results.append(decoded.get("cpe23Type"))

        if decoded.get("cpe22Type") is not None:
            LOGGER.debug("Found CPE22")
            results.append(decoded.get("cpe22Type"))

        if results == []:
            LOGGER.debug("Nothing found")
            results.append([None, None, None])

        return results
[...]
    def parse_cyclonedx_spdx(self) -> [(str, str, str)]:
[...]
            # If Package URL or CPE record found, use this data in preference to package data
            ext_ref = package.get("externalreference")
            if ext_ref is not None:
                external_references = self.parse_ext_ref(ext_ref=ext_ref)
                # Store the data for each external reference
                for vendor, package_name, version in external_references:
                    # For any data not found in CPE or the Package URL get from package data
                    if not vendor:
                        pass  # Because no vendor was detected then all vendors with this named package
                        # will be included in the output.

                    if not package_name:
                        package_name = package["name"]

                    if (not version) and (package.get("version") is not None):
                        version = package["version"]
                    elif version is None:
                        LOGGER.debug(f"No version found in {package}")

                    if version:
                        # Found at least package and version, save the results
                        modules.append([vendor, package_name, version])

self.parse_ext_ref(ext_ref=ext_ref) will return [[None, None, None]] in case there's no cpe23type/cpe22type/purl found in the externalReferences and then iterate over it and still add to modules array. I think we should maybe not do anything if no cpe22type/cpe23type/purl is found?

cc @mmind

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions