From a9b32473be2e3a92d9451be0f041f5503e32ef47 Mon Sep 17 00:00:00 2001 From: gruebel Date: Mon, 30 Oct 2023 22:32:21 +0000 Subject: [PATCH] fix duplicate components in CycloneDX report --- checkov/common/output/cyclonedx.py | 10 +-- tests/common/output/test_cyclonedx_report.py | 68 ++++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/checkov/common/output/cyclonedx.py b/checkov/common/output/cyclonedx.py index 0224dd09812..092af9363b0 100644 --- a/checkov/common/output/cyclonedx.py +++ b/checkov/common/output/cyclonedx.py @@ -100,12 +100,8 @@ def create_bom(self) -> Bom: continue component = self.create_component(check_type=report.check_type, resource=check) - if bom.has_component(component=component): - component = ( - bom.get_component_by_purl( # type:ignore[assignment] # the previous line checks, if exists - purl=component.purl - ) - ) + if existing_component := bom.get_component_by_purl(purl=component.purl): + component = existing_component else: bom.components.add(component) @@ -121,7 +117,7 @@ def create_bom(self) -> Bom: for resource in sorted(report.extra_resources): component = self.create_component(check_type=report.check_type, resource=resource) - if not bom.has_component(component=component): + if not bom.get_component_by_purl(purl=component.purl): bom.components.add(component) if is_image_report: diff --git a/tests/common/output/test_cyclonedx_report.py b/tests/common/output/test_cyclonedx_report.py index aff2d51ecfe..dd54de44beb 100644 --- a/tests/common/output/test_cyclonedx_report.py +++ b/tests/common/output/test_cyclonedx_report.py @@ -197,6 +197,74 @@ def test_sca_packages_cyclonedx_bom(): assert record.file_line_range == [2, 6] assert output + +def test_duplicate_sca_packages_cyclonedx_bom(): + # given + rootless_file_path = "requirements.txt" + file_abs_path = "/path/to/requirements.txt" + check_class = "checkov.sca_package.scanner.Scanner" + vulnerability_details = { + "id": "CVE-2019-19844", + "status": "fixed in 3.0.1, 2.2.9, 1.11.27", + "cvss": 9.8, + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "description": "Django before 1.11.27, 2.x before 2.2.9, and 3.x before 3.0.1 allows account takeover. ...", + "severity": "moderate", + "packageName": "django", + "packageVersion": "1.2", + "link": "https://nvd.nist.gov/vuln/detail/CVE-2019-19844", + "riskFactors": ["Attack complexity: low", "Attack vector: network", "Critical severity", "Has fix"], + "impactedVersions": ["<1.11.27"], + "publishedDate": "2019-12-18T20:15:00+01:00", + "discoveredDate": "2019-12-18T19:15:00Z", + "fixDate": "2019-12-18T20:15:00+01:00", + } + package_1 = { + "package_registry": "https://registry.npmjs.org/", + "is_private_registry": False, + "linesNumbers": [2, 3], + } + package_2 = { + "package_registry": "https://registry.npmjs.org/", + "is_private_registry": False, + "linesNumbers": [5, 6], + } + + record_1 = create_report_cve_record( + rootless_file_path=rootless_file_path, + file_abs_path=file_abs_path, + check_class=check_class, + vulnerability_details=vulnerability_details, + licenses="OSI_BDS", + package=package_1, + file_line_range=get_package_lines(package_1), + ) + record_2 = create_report_cve_record( + rootless_file_path=rootless_file_path, + file_abs_path=file_abs_path, + check_class=check_class, + vulnerability_details=vulnerability_details, + licenses="OSI_BDS", + package=package_2, + file_line_range=get_package_lines(package_2), + ) + + report = Report(CheckType.SCA_PACKAGE) + report.add_resource(record_1.resource) + report.add_record(record_1) + report.add_resource(record_2.resource) + report.add_record(record_2) + + # when + cyclonedx = CycloneDX([report], "repoid/test") + + # then + assert len(cyclonedx.bom.components) == 1 + + component = next(iter(cyclonedx.bom.components)) + assert component.bom_ref.value == "pkg:pypi/repoid/test/requirements.txt/django@1.2" + + def test_create_schema_version_1_3(mocker: MockerFixture): # given test_file = Path(__file__).parent / "fixtures/main.tf"