diff --git a/component_catalog/models.py b/component_catalog/models.py index 18dda9cb..ab19c8a3 100644 --- a/component_catalog/models.py +++ b/component_catalog/models.py @@ -745,6 +745,9 @@ def get_export_spdx_url(self): def get_export_cyclonedx_url(self): return self.get_url("export_cyclonedx") + def get_export_vex_url(self): + return self.get_url("export_vex") + def get_about_files(self): """ Return the list of all AboutCode files from all the Packages diff --git a/component_catalog/templates/component_catalog/package_vex_form.html b/component_catalog/templates/component_catalog/package_vex_form.html new file mode 100644 index 00000000..75bca7b1 --- /dev/null +++ b/component_catalog/templates/component_catalog/package_vex_form.html @@ -0,0 +1,4 @@ +{% extends "object_form.html" %} +{% block javascripts %} + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/component_catalog/views.py b/component_catalog/views.py index 5df07c87..f98429f6 100644 --- a/component_catalog/views.py +++ b/component_catalog/views.py @@ -75,6 +75,7 @@ from dejacode_toolkit.scancodeio import ScanCodeIO from dejacode_toolkit.scancodeio import get_package_download_url from dejacode_toolkit.scancodeio import get_scan_results_as_file_url +from dejacode_toolkit.vex import create_auto_vex from dejacode_toolkit.vulnerablecode import VulnerableCode from dje import tasks from dje.client_data import add_client_data @@ -857,7 +858,6 @@ def get_vulnerabilities_tab_fields(self, vulnerabilities): vulnerability_fields = self.get_vulnerability_fields(vulnerability, dataspace) fields.extend(vulnerability_fields) vulnerabilities_count += 1 - return fields, vulnerabilities_count def get_context_data(self, **kwargs): @@ -1452,6 +1452,8 @@ def get_vulnerabilities_tab_fields(self, vulnerabilities): fields = [] vulnerabilities_count = 0 + create_auto_vex(self.object, vulnerabilities) + for entry in vulnerabilities: unresolved = entry.get("affected_by_vulnerabilities", []) for vulnerability in unresolved: diff --git a/dejacode_toolkit/tests/test_vex.py b/dejacode_toolkit/tests/test_vex.py new file mode 100644 index 00000000..fbb2ae79 --- /dev/null +++ b/dejacode_toolkit/tests/test_vex.py @@ -0,0 +1,214 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# DejaCode is a trademark of nexB Inc. +# SPDX-License-Identifier: AGPL-3.0-only +# See https://github.com/nexB/dejacode for support or download. +# See https://aboutcode.org for more information about AboutCode FOSS projects. +# + + +import json +import os + +from django.contrib.auth import get_user_model +from django.test import TestCase + +from cyclonedx.output.json import SchemaVersion1Dot4 +from serializable import _SerializableJsonEncoder + +from component_catalog.models import Package +from dejacode_toolkit import vex +from dejacode_toolkit.vex import VEXCycloneDX +from dejacode_toolkit.vex import vulnerability_format_vcic_to_cyclonedx +from dje.models import Dataspace +from dje.tests import create_user +from product_portfolio.models import Product +from product_portfolio.models import ProductPackage +from product_portfolio.models import ProductPackageVEX + +User = get_user_model() + + +class VEXTestCase(TestCase): + def setUp(self): + self.nexb_dataspace = Dataspace.objects.create(name="nexB") + self.nexb_user = User.objects.create_superuser( + "nexb_user", "test@test.com", "t3st", self.nexb_dataspace + ) + self.basic_user = create_user("basic_user", self.nexb_dataspace) + self.product1 = Product.objects.create( + name="Product1 With Space", version="1.0", dataspace=self.nexb_dataspace + ) + self.package1 = Package.objects.create(filename="package1", dataspace=self.nexb_dataspace) + + self.productpacakge1 = ProductPackage.objects.create( + product=self.product1, package=self.package1, dataspace=self.nexb_dataspace + ) + self.vex1 = ProductPackageVEX.objects.create( + dataspace=self.productpacakge1.dataspace, + productpackage=self.productpacakge1, + vulnerability_id="VCID-111c-u9bh-aaac", + responses=["CNF"], + justification="CNP", + detail=( + "Automated dataflow analysis and manual " + "code review indicates that the vulnerable code is not reachable," + " either directly or indirectly." + ), + ) + + def test_create_auto_vex1(self): + vulnerabilities = [ + { + "affected_by_vulnerabilities": [ + { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332", + "vulnerability_id": "VCID-111c-u9bh-aaac", + } + ] + }, + { + "affected_by_vulnerabilities": [ + { + "url": "https://public.vulnerablecode.io/api/vulnerabilities/121331", + "vulnerability_id": "VCID-uxf9-7c97-aaaj", + } + ] + }, + ] + assert ProductPackageVEX.objects.count() == 1 + vex.create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 2 + + # run create_auto_vex agian and make sure that the databse ignore errors + vex.create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 2 + + def test_create_auto_vex2(self): + # duplicated vulnerability + vulnerabilities = [ + { + "affected_by_vulnerabilities": [ + { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332", + "vulnerability_id": "VCID-111c-u9bh-aaac", + } + ] + }, + { + "affected_by_vulnerabilities": [ + { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332", + "vulnerability_id": "VCID-111c-u9bh-aaac", + } + ] + }, + ] + assert ProductPackageVEX.objects.count() == 1 + vex.create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 1 + + def test_get_references_and_rating(self): + references = [ + { + "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136", + "reference_id": "CVE-2017-1000136", + "scores": [ + { + "value": "5.0", + "scoring_system": "cvssv2", + "scoring_elements": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + }, + { + "value": "5.3", + "scoring_system": "cvssv3", + "scoring_elements": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + }, + ], + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136", + } + ] + ref, rate = vex.get_references_and_rating(references) + + assert json.dumps( + ref, + cls=_SerializableJsonEncoder, + view_=SchemaVersion1Dot4, + ) == json.dumps( + [ + { + "id": "CVE-2017-1000136", + "source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"}, + } + ] + ) + + assert json.dumps( + rate, + cls=_SerializableJsonEncoder, + view_=SchemaVersion1Dot4, + ) == json.dumps( + [ + { + "method": "CVSSv2", + "score": "5.0", + "source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"}, + "vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + }, + { + "method": "CVSSv3", + "score": "5.3", + "source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"}, + "vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + }, + ] + ) + + def test_vulnerability_format_vcic_to_cyclonedx1(self): + vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul1.json") + with open(vul_data_path) as f: + vcio_vulnerability = json.load(f) + + vulnerability = vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, self.vex1) + + cyclonedx_vul_data_path = os.path.join( + os.path.dirname(__file__), "testfiles", "cyclonedx_vul1.json" + ) + with open(cyclonedx_vul_data_path) as f: + cyclonedx_vul = json.load(f) + + assert json.dumps( + vulnerability, + cls=_SerializableJsonEncoder, + view_=SchemaVersion1Dot4, + ) == json.dumps(cyclonedx_vul) + + def test_vulnerability_format_vcic_to_cyclonedx2(self): + vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul2.json") + with open(vul_data_path) as f: + vcio_vulnerability = json.load(f) + + vulnerability = vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, self.vex1) + + cyclonedx_vul_data_path = os.path.join( + os.path.dirname(__file__), "testfiles", "cyclonedx_vul2.json" + ) + with open(cyclonedx_vul_data_path) as f: + cyclonedx_vul = json.load(f) + + assert json.dumps( + vulnerability, + cls=_SerializableJsonEncoder, + view_=SchemaVersion1Dot4, + ) == json.dumps(cyclonedx_vul) + + def test_vex_cyclonedx_export(self): + vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul1.json") + with open(vul_data_path) as f: + vcio_vulnerability = json.load(f) + + vex_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vex1.json") + with open(vex_data_path) as f: + vex_data = json.load(f) + + assert VEXCycloneDX().export([vcio_vulnerability], [self.vex1]) == json.dumps(vex_data) diff --git a/dejacode_toolkit/tests/testfiles/cyclonedx_vul1.json b/dejacode_toolkit/tests/testfiles/cyclonedx_vul1.json new file mode 100644 index 00000000..90d981f2 --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/cyclonedx_vul1.json @@ -0,0 +1,151 @@ +{ + "analysis": { + "detail": "Automated dataflow analysis and manual code review indicates that the vulnerable code is not reachable, either directly or indirectly.", + "justification": "code_not_present", + "response": [ + "can_not_fix" + ] + }, + "cwes": [ + 613 + ], + "description": "Mahara 1.8 before 1.8.6 and 1.9 before 1.9.4 and 1.10 before 1.10.1 and 15.04 before 15.04.0 are vulnerable to old sessions not being invalidated after a password change.", + "id": "VCID-111c-u9bh-aaac", + "ratings": [ + { + "method": "CVSSv2", + "score": "4.3", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + }, + "vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + { + "method": "CVSSv3", + "score": "6.5", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + }, + "vector": "AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N" + } + ], + "references": [ + { + "id": "", + "source": { + "url": "https://bugs.launchpad.net/mahara/+bug/1363873" + } + }, + { + "id": "CVE-2017-1000136", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*" + } + } + ], + "source": { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332" + } +} \ No newline at end of file diff --git a/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json b/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json new file mode 100644 index 00000000..21156207 --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json @@ -0,0 +1,287 @@ +{ + "affects": [ + { + "ref": "urn:cdx:serialNumber/version#bom-ref", + "versions": [ + { + "status": "affected", + "version": "0.1" + }, + { + "status": "affected", + "version": "0.10" + }, + { + "status": "affected", + "version": "0.10.1" + }, + { + "status": "affected", + "version": "0.11" + }, + { + "status": "affected", + "version": "0.11.1" + }, + { + "status": "affected", + "version": "0.12" + }, + { + "status": "affected", + "version": "0.12.1" + }, + { + "status": "affected", + "version": "0.12.2" + }, + { + "status": "affected", + "version": "0.12.3" + }, + { + "status": "affected", + "version": "0.12.4" + }, + { + "status": "affected", + "version": "0.12.5" + }, + { + "status": "affected", + "version": "0.2" + }, + { + "status": "affected", + "version": "0.3" + }, + { + "status": "affected", + "version": "0.3.1" + }, + { + "status": "affected", + "version": "0.4" + }, + { + "status": "affected", + "version": "0.5" + }, + { + "status": "affected", + "version": "0.5.1" + }, + { + "status": "affected", + "version": "0.5.2" + }, + { + "status": "affected", + "version": "0.6" + }, + { + "status": "affected", + "version": "0.6.1" + }, + { + "status": "affected", + "version": "0.7" + }, + { + "status": "affected", + "version": "0.7.1" + }, + { + "status": "affected", + "version": "0.7.2" + }, + { + "status": "affected", + "version": "0.8" + }, + { + "status": "affected", + "version": "0.8.1" + }, + { + "status": "affected", + "version": "0.9" + }, + { + "status": "affected", + "version": "1.0" + }, + { + "status": "affected", + "version": "1.0.1" + }, + { + "status": "affected", + "version": "1.0.2" + }, + { + "status": "affected", + "version": "1.0.2-3" + }, + { + "status": "affected", + "version": "1.0.3" + }, + { + "status": "affected", + "version": "1.0.4" + }, + { + "status": "affected", + "version": "1.1.0" + }, + { + "status": "affected", + "version": "1.1.1" + }, + { + "status": "affected", + "version": "1.1.2" + }, + { + "status": "affected", + "version": "1.1.2-2" + }, + { + "status": "affected", + "version": "1.1.3" + }, + { + "status": "affected", + "version": "1.1.4" + }, + { + "status": "affected", + "version": "1:0.10.1-7" + }, + { + "status": "affected", + "version": "1:1.0.2-8" + }, + { + "status": "affected", + "version": "1:1.1.2-6" + }, + { + "status": "affected", + "version": "1:2.0.1-3" + }, + { + "status": "affected", + "version": "2.0.0" + }, + { + "status": "affected", + "version": "2.0.0rc1" + }, + { + "status": "affected", + "version": "2.0.0rc2" + }, + { + "status": "affected", + "version": "2.0.1" + }, + { + "status": "affected", + "version": "2.0.2" + }, + { + "status": "affected", + "version": "2.0.3" + }, + { + "status": "affected", + "version": "2.1.0" + }, + { + "status": "affected", + "version": "2.1.1" + }, + { + "status": "affected", + "version": "2.1.2" + }, + { + "status": "affected", + "version": "2.1.3" + }, + { + "status": "affected", + "version": "2.2.0" + }, + { + "status": "affected", + "version": "2.2.1" + }, + { + "status": "affected", + "version": "2.2.2" + }, + { + "status": "affected", + "version": "2.2.2-2" + }, + { + "status": "affected", + "version": "2.2.3" + }, + { + "status": "affected", + "version": "2.2.4" + }, + { + "status": "affected", + "version": "2.3.0" + }, + { + "status": "affected", + "version": "2.3.1" + }, + { + "status": "affected", + "version": "2:2.0.1-4.el9" + } + ] + } + ], + "analysis": { + "detail": "Automated dataflow analysis and manual code review indicates that the vulnerable code is not reachable, either directly or indirectly.", + "justification": "code_not_present", + "response": [ + "can_not_fix" + ] + }, + "cwes": [ + 488, + 539 + ], + "description": "Flask vulnerable to possible disclosure of permanent session cookie due to missing Vary: Cookie header", + "id": "VCID-111c-u9bh-aaac", + "ratings": [ + { + "method": "CVSSv3", + "score": "7.5", + "source": { + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json" + }, + "vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + } + ], + "references": [ + { + "id": "", + "source": { + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json" + } + } + ], + "source": { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/457133?format=json" + } +} \ No newline at end of file diff --git a/dejacode_toolkit/tests/testfiles/vcio_vul1.json b/dejacode_toolkit/tests/testfiles/vcio_vul1.json new file mode 100644 index 00000000..e672006d --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/vcio_vul1.json @@ -0,0 +1,147 @@ +{ + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332", + "vulnerability_id": "VCID-111c-u9bh-aaac", + "summary": "Mahara 1.8 before 1.8.6 and 1.9 before 1.9.4 and 1.10 before 1.10.1 and 15.04 before 15.04.0 are vulnerable to old sessions not being invalidated after a password change.", + "aliases": [ + { + "alias": "CVE-2017-1000136" + } + ], + "fixed_packages": [], + "affected_packages": [], + "references": [ + { + "reference_url": "https://bugs.launchpad.net/mahara/+bug/1363873", + "reference_id": "", + "scores": [], + "url": "https://bugs.launchpad.net/mahara/+bug/1363873" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136", + "reference_id": "CVE-2017-1000136", + "scores": [ + { + "value": "4.3", + "scoring_system": "cvssv2", + "scoring_elements": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + { + "value": "6.5", + "scoring_system": "cvssv3", + "scoring_elements": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N" + } + ], + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + } + ], + "weaknesses": [ + { + "cwe_id": 613, + "name": "Insufficient Session Expiration", + "description": "According to WASC, Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization." + } + ], + "resource_url": "http://public.vulnerablecode.io/vulnerabilities/VCID-111c-u9bh-aaac" +} \ No newline at end of file diff --git a/dejacode_toolkit/tests/testfiles/vcio_vul2.json b/dejacode_toolkit/tests/testfiles/vcio_vul2.json new file mode 100644 index 00000000..7bfeef1a --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/vcio_vul2.json @@ -0,0 +1,1015 @@ +{ + "url": "http://public.vulnerablecode.io/api/vulnerabilities/457133?format=json", + "vulnerability_id": "VCID-111c-u9bh-aaac", + "summary": "Flask vulnerable to possible disclosure of permanent session cookie due to missing Vary: Cookie header", + "aliases": [ + { + "alias": "CVE-2023-30861" + }, + { + "alias": "GHSA-m2qf-hxjv-5gpq" + }, + { + "alias": "PYSEC-2023-62" + } + ], + "fixed_packages": [ + { + "url": "http://public.vulnerablecode.io/api/packages/625069?format=json", + "purl": "pkg:deb/debian/flask@1.0.2-3?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.0.2-3%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/625070?format=json", + "purl": "pkg:deb/debian/flask@1.0.2-3%2Bdeb10u1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.0.2-3%252Bdeb10u1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/625067?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/728786?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2%2Bdeb11u1", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2%252Bdeb11u1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/625068?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2%2Bdeb11u1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2%252Bdeb11u1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/597957?format=json", + "purl": "pkg:deb/debian/flask@2.2.2-3?distro=sid", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@2.2.2-3%3Fdistro=sid" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/625066?format=json", + "purl": "pkg:deb/debian/flask@2.2.2-3?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@2.2.2-3%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/639141?format=json", + "purl": "pkg:deb/debian/flask@2.2.5-1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@2.2.5-1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/808362?format=json", + "purl": "pkg:deb/debian/flask@3.0.2-1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@3.0.2-1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/815308?format=json", + "purl": "pkg:deb/debian/flask@3.0.3-1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@3.0.3-1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/642099?format=json", + "purl": "pkg:pypi/flask@2.2.5", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.5" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606889?format=json", + "purl": "pkg:pypi/flask@2.3.2", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.3.2" + } + ], + "affected_packages": [ + { + "url": "http://public.vulnerablecode.io/api/packages/591807?format=json", + "purl": "pkg:deb/debian/flask@1.0.2-3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.0.2-3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/366672?format=json", + "purl": "pkg:deb/debian/flask@1.0.2-3?distro=sid", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.0.2-3%3Fdistro=sid" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/728785?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/366671?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2?distro=sid", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2%3Fdistro=sid" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/559244?format=json", + "purl": "pkg:deb/debian/flask@2.2.2-2?distro=sid", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@2.2.2-2%3Fdistro=sid" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110820?format=json", + "purl": "pkg:pypi/flask@0.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110836?format=json", + "purl": "pkg:pypi/flask@0.10", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.10" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110837?format=json", + "purl": "pkg:pypi/flask@0.10.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.10.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110838?format=json", + "purl": "pkg:pypi/flask@0.11", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.11" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110839?format=json", + "purl": "pkg:pypi/flask@0.11.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.11.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110840?format=json", + "purl": "pkg:pypi/flask@0.12", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110841?format=json", + "purl": "pkg:pypi/flask@0.12.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110842?format=json", + "purl": "pkg:pypi/flask@0.12.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110843?format=json", + "purl": "pkg:pypi/flask@0.12.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/112303?format=json", + "purl": "pkg:pypi/flask@0.12.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/112304?format=json", + "purl": "pkg:pypi/flask@0.12.5", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.5" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110821?format=json", + "purl": "pkg:pypi/flask@0.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110822?format=json", + "purl": "pkg:pypi/flask@0.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110823?format=json", + "purl": "pkg:pypi/flask@0.3.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.3.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110824?format=json", + "purl": "pkg:pypi/flask@0.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110825?format=json", + "purl": "pkg:pypi/flask@0.5", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.5" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110826?format=json", + "purl": "pkg:pypi/flask@0.5.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.5.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110827?format=json", + "purl": "pkg:pypi/flask@0.5.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.5.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110828?format=json", + "purl": "pkg:pypi/flask@0.6", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.6" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110829?format=json", + "purl": "pkg:pypi/flask@0.6.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.6.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110830?format=json", + "purl": "pkg:pypi/flask@0.7", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.7" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110831?format=json", + "purl": "pkg:pypi/flask@0.7.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.7.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110832?format=json", + "purl": "pkg:pypi/flask@0.7.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.7.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110833?format=json", + "purl": "pkg:pypi/flask@0.8", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.8" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110834?format=json", + "purl": "pkg:pypi/flask@0.8.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.8.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110835?format=json", + "purl": "pkg:pypi/flask@0.9", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.9" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/112305?format=json", + "purl": "pkg:pypi/flask@1.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606890?format=json", + "purl": "pkg:pypi/flask@1.0.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606891?format=json", + "purl": "pkg:pypi/flask@1.0.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606892?format=json", + "purl": "pkg:pypi/flask@1.0.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606893?format=json", + "purl": "pkg:pypi/flask@1.0.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606894?format=json", + "purl": "pkg:pypi/flask@1.1.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606895?format=json", + "purl": "pkg:pypi/flask@1.1.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606896?format=json", + "purl": "pkg:pypi/flask@1.1.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606897?format=json", + "purl": "pkg:pypi/flask@1.1.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606898?format=json", + "purl": "pkg:pypi/flask@1.1.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606901?format=json", + "purl": "pkg:pypi/flask@2.0.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606899?format=json", + "purl": "pkg:pypi/flask@2.0.0rc1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.0rc1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606900?format=json", + "purl": "pkg:pypi/flask@2.0.0rc2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.0rc2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606902?format=json", + "purl": "pkg:pypi/flask@2.0.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606903?format=json", + "purl": "pkg:pypi/flask@2.0.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606904?format=json", + "purl": "pkg:pypi/flask@2.0.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606905?format=json", + "purl": "pkg:pypi/flask@2.1.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.1.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606906?format=json", + "purl": "pkg:pypi/flask@2.1.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.1.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606907?format=json", + "purl": "pkg:pypi/flask@2.1.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.1.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606908?format=json", + "purl": "pkg:pypi/flask@2.1.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.1.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606909?format=json", + "purl": "pkg:pypi/flask@2.2.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606910?format=json", + "purl": "pkg:pypi/flask@2.2.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606911?format=json", + "purl": "pkg:pypi/flask@2.2.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606912?format=json", + "purl": "pkg:pypi/flask@2.2.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606913?format=json", + "purl": "pkg:pypi/flask@2.2.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606887?format=json", + "purl": "pkg:pypi/flask@2.3.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.3.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606888?format=json", + "purl": "pkg:pypi/flask@2.3.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.3.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686987?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:0.10.1-7?arch=el7_9", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:0.10.1-7%3Farch=el7_9" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686989?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:1.0.2-8?arch=el8ost", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:1.0.2-8%3Farch=el8ost" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686988?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:1.1.2-6?arch=el8", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:1.1.2-6%3Farch=el8" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686990?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:1.1.2-6?arch=el9ost", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:1.1.2-6%3Farch=el9ost" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686985?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:2.0.1-3?arch=el9", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:2.0.1-3%3Farch=el9" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686986?format=json", + "purl": "pkg:rpm/redhat/python-flask@2:2.0.1-4.el9?arch=2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-ccmw-8ht8-aaaq" + }, + { + "vulnerability": "VCID-kuxv-41y8-aaaj" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@2:2.0.1-4.el9%3Farch=2" + } + ], + "references": [ + { + "reference_url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json", + "reference_id": "", + "scores": [ + { + "value": "7.5", + "scoring_system": "cvssv3", + "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + } + ], + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json" + } + ], + "weaknesses": [ + { + "cwe_id": 539, + "name": "Use of Persistent Cookies Containing Sensitive Information", + "description": "The web application uses persistent cookies, but the cookies contain sensitive information." + }, + { + "cwe_id": 488, + "name": "Exposure of Data Element to Wrong Session", + "description": "The product does not sufficiently enforce boundaries between the states of different sessions, causing data to be provided to, or used by, the wrong session." + } + ], + "resource_url": "http://public.vulnerablecode.io/vulnerabilities/VCID-z6fe-2j8a-aaak" +} \ No newline at end of file diff --git a/dejacode_toolkit/tests/testfiles/vex1.json b/dejacode_toolkit/tests/testfiles/vex1.json new file mode 100644 index 00000000..7e43f358 --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/vex1.json @@ -0,0 +1,158 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "vulnerabilities": [ + { + "analysis": { + "detail": "Automated dataflow analysis and manual code review indicates that the vulnerable code is not reachable, either directly or indirectly.", + "justification": "code_not_present", + "response": [ + "can_not_fix" + ] + }, + "cwes": [ + 613 + ], + "description": "Mahara 1.8 before 1.8.6 and 1.9 before 1.9.4 and 1.10 before 1.10.1 and 15.04 before 15.04.0 are vulnerable to old sessions not being invalidated after a password change.", + "id": "VCID-111c-u9bh-aaac", + "ratings": [ + { + "method": "CVSSv2", + "score": "4.3", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + }, + "vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + { + "method": "CVSSv3", + "score": "6.5", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + }, + "vector": "AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N" + } + ], + "references": [ + { + "id": "", + "source": { + "url": "https://bugs.launchpad.net/mahara/+bug/1363873" + } + }, + { + "id": "CVE-2017-1000136", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*" + } + } + ], + "source": { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332" + } + } + ] +} \ No newline at end of file diff --git a/dejacode_toolkit/vex.py b/dejacode_toolkit/vex.py new file mode 100644 index 00000000..11254483 --- /dev/null +++ b/dejacode_toolkit/vex.py @@ -0,0 +1,290 @@ +import json +from dataclasses import dataclass +from dataclasses import field +from typing import List +from typing import Literal + +from cyclonedx.model.vulnerability import BomTarget +from cyclonedx.model.vulnerability import BomTargetVersionRange +from cyclonedx.model.vulnerability import ImpactAnalysisAffectedStatus +from cyclonedx.model.vulnerability import ImpactAnalysisJustification +from cyclonedx.model.vulnerability import ImpactAnalysisResponse +from cyclonedx.model.vulnerability import ImpactAnalysisState +from cyclonedx.model.vulnerability import Vulnerability +from cyclonedx.model.vulnerability import VulnerabilityAnalysis +from cyclonedx.model.vulnerability import VulnerabilityRating +from cyclonedx.model.vulnerability import VulnerabilityReference +from cyclonedx.model.vulnerability import VulnerabilityScoreSource +from cyclonedx.model.vulnerability import VulnerabilitySeverity +from cyclonedx.model.vulnerability import VulnerabilitySource +from cyclonedx.output.json import SchemaVersion1Dot4 +from cyclonedx.output.json import SchemaVersion1Dot5 +from cyclonedx.output.json import SchemaVersion1Dot6 +from packageurl import PackageURL +from serializable import _SerializableJsonEncoder + +from product_portfolio.models import ProductPackage +from product_portfolio.models import ProductPackageVEX + +# from cyclonedx.model.vulnerability import VulnerabilityCredits +# from cyclonedx.model.vulnerability import VulnerabilityAdvisory + +SCORING_SYSTEMS_CYCLONEDX = { + "cvssv2": VulnerabilityScoreSource.CVSS_V2, + "cvssv3": VulnerabilityScoreSource.CVSS_V3, + "cvssv3.1": VulnerabilityScoreSource.CVSS_V3_1, + "cvssv4": VulnerabilityScoreSource.CVSS_V4, + "owasp": VulnerabilityScoreSource.OWASP, + "ssvc": VulnerabilityScoreSource.SSVC, + "other": VulnerabilityScoreSource.OTHER, +} + +GENERIC_SEVERITIES_VALUE_CYCLONEDX = { + "none": VulnerabilitySeverity.NONE, + "info": VulnerabilitySeverity.INFO, + "low": VulnerabilitySeverity.LOW, + "meduim": VulnerabilitySeverity.MEDIUM, + "high": VulnerabilitySeverity.HIGH, + "critical": VulnerabilitySeverity.CRITICAL, + "unknow": VulnerabilitySeverity.UNKNOWN, +} + +STATUS_VEX_CYCLONEDX = { + True: ImpactAnalysisAffectedStatus.AFFECTED, + False: ImpactAnalysisAffectedStatus.UNAFFECTED, + # "unknow": ImpactAnalysisAffectedStatus.UNKNOWN, default +} + +STATE_VEX_CYCLONEDX = { + "R": ImpactAnalysisState.RESOLVED, + "RWP": ImpactAnalysisState.RESOLVED_WITH_PEDIGREE, + "E": ImpactAnalysisState.EXPLOITABLE, + "IT": ImpactAnalysisState.IN_TRIAGE, + "FP": ImpactAnalysisState.FALSE_POSITIVE, + "NA": ImpactAnalysisState.NOT_AFFECTED, +} + +JUSTIFICATION_VEX_CYCLONEDX = { + "CNP": ImpactAnalysisJustification.CODE_NOT_PRESENT, + "CNR": ImpactAnalysisJustification.CODE_NOT_REACHABLE, + "PP": ImpactAnalysisJustification.PROTECTED_AT_PERIMITER, + "PR": ImpactAnalysisJustification.PROTECTED_AT_RUNTIME, + "PC": ImpactAnalysisJustification.PROTECTED_BY_COMPILER, + "PMC": ImpactAnalysisJustification.PROTECTED_BY_MITIGATING_CONTROL, + "RC": ImpactAnalysisJustification.REQUIRES_CONFIGURATION, + "RD": ImpactAnalysisJustification.REQUIRES_DEPENDENCY, + "RE": ImpactAnalysisJustification.REQUIRES_ENVIRONMENT, +} + +RESPONSES_VEX_CYCLONEDX = { + "CNF": ImpactAnalysisResponse.CAN_NOT_FIX, + "RB": ImpactAnalysisResponse.ROLLBACK, + "U": ImpactAnalysisResponse.UPDATE, + "WNF": ImpactAnalysisResponse.WILL_NOT_FIX, + "WA": ImpactAnalysisResponse.WORKAROUND_AVAILABLE, +} + + +def create_auto_vex(package, vulnerabilities): + # automatically create a VEX for each product package that has a vulnerability + vex_objects = [] + vulnerability_ids = [] + for entry in vulnerabilities: + unresolved = entry.get("affected_by_vulnerabilities", []) + for vulnerability in unresolved: + vulnerability_id = vulnerability.get("vulnerability_id") + if vulnerability_id: + vulnerability_ids.append(vulnerability_id) + + productpackages = ProductPackage.objects.filter(package=package) + for productpackage in productpackages: + for vulnerability_id in vulnerability_ids: + vex_objects.append( + ProductPackageVEX( + dataspace=productpackage.dataspace, + productpackage=productpackage, + vulnerability_id=vulnerability_id, + ) + ) + + ProductPackageVEX.objects.bulk_create(vex_objects, ignore_conflicts=True) + + +def vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, vex: ProductPackageVEX): + """Change the VCIO format of the vulnerability to CycloneDX and add the vex vulnerability""" + vulnerability_source = VulnerabilitySource( + url=vcio_vulnerability.get("url"), + ) + + references = vcio_vulnerability.get("references") or [] + vulnerability_reference, vulnerability_ratings = get_references_and_rating(references) + + state = STATE_VEX_CYCLONEDX.get(vex.state) + justification = JUSTIFICATION_VEX_CYCLONEDX.get(vex.justification) + + responses = [] + for vex_res in vex.responses or []: + response = RESPONSES_VEX_CYCLONEDX.get(vex_res) + if response: + responses.append(response) + + vulnerability_analysis = VulnerabilityAnalysis( + state=state, + responses=responses, + justification=justification, + detail=vex.detail, + ) + + # vulnerability_advisory = VulnerabilityAdvisory() # ignore + # vulnerability_credits = VulnerabilityCredits() # ignore + + # property = Property() # ignore + # tool = Tool() # ignore + + bom_targets = [] + versions = [] + for affected_package in vcio_vulnerability.get("affected_packages") or []: + is_vulnerable = affected_package.get("is_vulnerable") + status = STATUS_VEX_CYCLONEDX.get(is_vulnerable, ImpactAnalysisAffectedStatus.UNKNOWN) + purl_string = affected_package.get("purl") + + vul_purl = None + if purl_string: + vul_purl = PackageURL.from_string(purl_string) + + versions.append(BomTargetVersionRange(version=vul_purl.version, status=status)) + + if versions: + bom_target = BomTarget(ref="urn:cdx:serialNumber/version#bom-ref", versions=versions) + bom_targets.append(bom_target) + + weaknesses = vcio_vulnerability.get("weaknesses") or [] + cwes = get_cwes(weaknesses) + + vulnerability = Vulnerability( + # bom_ref="", + id=vcio_vulnerability.get("vulnerability_id"), + source=vulnerability_source, + references=vulnerability_reference, + ratings=vulnerability_ratings, + cwes=cwes, + description=vcio_vulnerability.get("summary") or "", + # detail=detail, + # recommendation="", + # advisories=advisories, + # created=created, + # published=published, + # updated=updated, + # credits=credits, + # tools=tools, + # properties=properties, + # Deprecated Parameters kept for backwards compatibility + analysis=vulnerability_analysis, + affects=bom_targets, + # source_name=source_name, + # source_url=source_url, + # recommendations=recommendations, + ) + return vulnerability + + +def get_cwes(weaknesses): + """ + Get the list of cwes number using vulnerability weaknesses + >>> get_cwes([{"cwe_id": 613,"name": "..."}]}) + [613] + >>> get_cwes([{"cwe_id": 613,"name": "..."}, {"cwe_id": 79,"name": "..."}]) + [613, 79] + >>> get_cwes([]) + [] + """ + cwes = [] + for weakness in weaknesses: + cwe_id = weakness.get("cwe_id") + if cwe_id: + cwes.append(cwe_id) + return cwes + + +def is_float(s): + try: + float(s) + return True + except ValueError: + return False + + +def get_references_and_rating(references): + cyclonedx_references, cyclonedx_rating = [], [] + for ref in references: + source = VulnerabilitySource(url=ref.get("url")) + cyclonedx_references.append( + VulnerabilityReference(id=ref.get("reference_id"), source=source) + ) + + for score in ref.get("scores") or []: + vul_source = VulnerabilitySource(url=ref.get("reference_url")) + + value = score.get("value") + scoring_system = score.get("scoring_system") + vector = score.get("scoring_elements") + + vul_severity = None + if scoring_system in SCORING_SYSTEMS_CYCLONEDX: + vul_method = SCORING_SYSTEMS_CYCLONEDX.get(scoring_system) + + if value.lower() in GENERIC_SEVERITIES_VALUE_CYCLONEDX: + vul_method = SCORING_SYSTEMS_CYCLONEDX.get(scoring_system) + vul_severity = GENERIC_SEVERITIES_VALUE_CYCLONEDX.get(value.lower()) + + vul_rating = VulnerabilityRating( + source=vul_source, + score=float(value) if is_float(value) else None, + severity=vul_severity, + method=vul_method, + vector=vector, + ) + cyclonedx_rating.append(vul_rating) + + return cyclonedx_references, cyclonedx_rating + + +@dataclass +class VEXCycloneDX: + """https://github.com/CycloneDX/bom-examples/tree/master/VEX""" + + vulnerabilities: List[Vulnerability] = field(default_factory=lambda: []) + bomFormat: str = "CycloneDX" + specVersion: Literal["1.4", "1.5", "1.6"] = "1.4" + version: int = 1 + + def export(self, vcio_vulnerabilities, vexs): + self.vulnerabilities = [] + schema_version = { + "1.4": SchemaVersion1Dot4, + "1.5": SchemaVersion1Dot5, + "1.6": SchemaVersion1Dot6, + }.get(self.specVersion) + + for vcio_vulnerability, vex in zip(vcio_vulnerabilities, vexs): + cyclonedx_vulnerability = vulnerability_format_vcic_to_cyclonedx( + vcio_vulnerability, vex + ) + self.vulnerabilities.append( + json.loads( + json.dumps( + cyclonedx_vulnerability, + cls=_SerializableJsonEncoder, + view_=schema_version, + ) + ) + ) + + return json.dumps( + { + "bomFormat": self.bomFormat, + "specVersion": self.specVersion, + "version": self.version, + "vulnerabilities": self.vulnerabilities, + } + ) diff --git a/dejacode_toolkit/vulnerablecode.py b/dejacode_toolkit/vulnerablecode.py index 7cefb243..0b2ad68b 100644 --- a/dejacode_toolkit/vulnerablecode.py +++ b/dejacode_toolkit/vulnerablecode.py @@ -5,7 +5,6 @@ # See https://github.com/nexB/dejacode for support or download. # See https://aboutcode.org for more information about AboutCode FOSS projects. # - from django.core.cache import caches from dejacode_toolkit import BaseService diff --git a/dje/tests/test_permissions.py b/dje/tests/test_permissions.py index 640e7922..f9de0b9b 100644 --- a/dje/tests/test_permissions.py +++ b/dje/tests/test_permissions.py @@ -140,6 +140,7 @@ def test_permissions_get_all_tabsets(self): "activity", "imports", "history", + "vex", ], } diff --git a/dje/views.py b/dje/views.py index 8438d206..4d34115b 100644 --- a/dje/views.py +++ b/dje/views.py @@ -75,6 +75,7 @@ from component_catalog.license_expression_dje import get_license_objects from dejacode_toolkit.purldb import PurlDB from dejacode_toolkit.scancodeio import ScanCodeIO +from dejacode_toolkit.vex import VEXCycloneDX from dejacode_toolkit.vulnerablecode import VulnerableCode from dje import outputs from dje.copier import COPY_DEFAULT_EXCLUDE @@ -116,6 +117,7 @@ from dje.utils import has_permission from dje.utils import queryset_to_changelist_href from dje.utils import str_to_id_list +from product_portfolio.models import ProductPackageVEX License = apps.get_model("license_library", "License") Request = apps.get_model("workflow", "Request") @@ -2364,3 +2366,44 @@ def get(self, request, *args, **kwargs): filename=outputs.get_cyclonedx_filename(instance), content_type="application/json", ) + + +class ExportVEXView( + LoginRequiredMixin, + DataspaceScopeMixin, + GetDataspacedObjectMixin, + BaseDetailView, +): + def get(self, request, *args, **kwargs): + product = self.get_object() + filename = f"dejacode_{product.dataspace.name}_{product._meta.model_name}_vex.json" + vulnerablecode = VulnerableCode(self.request.user) + + if not vulnerablecode.is_configured(): + raise Http404 + + vexs = ProductPackageVEX.objects.filter( + productpackage__product=product, dataspace=product.dataspace, ignored=False + ) + + vulnerabilities = [] + for vex in vexs: + url = f"{vulnerablecode.api_url}vulnerabilities/" + vulnerability = vulnerablecode.get_vulnerabilities( + url, field_name="vulnerability_id", field_value=vex.vulnerability_id + ) + vulnerabilities.append(vulnerability[0]) + + if not vulnerabilities: + raise Http404 + + spec_version = self.request.GET.get("spec_version") + vex = VEXCycloneDX(specVersion=spec_version) + vex_json = vex.export(vulnerabilities, vexs) + response = FileResponse( + vex_json, + filename=filename, + content_type="application/json", + ) + response["Content-Disposition"] = f'attachment; filename="{filename}"' + return response diff --git a/product_portfolio/forms.py b/product_portfolio/forms.py index 29d3681a..f5507a5d 100644 --- a/product_portfolio/forms.py +++ b/product_portfolio/forms.py @@ -51,6 +51,7 @@ from product_portfolio.models import Product from product_portfolio.models import ProductComponent from product_portfolio.models import ProductPackage +from product_portfolio.models import ProductPackageVEX from product_portfolio.models import ScanCodeProject @@ -944,3 +945,30 @@ def submit(self, product, user): scancodeproject_uuid=scancode_project.uuid, ) ) + + +class PackageVEXForm(DataspacedModelForm): + class Meta: + model = ProductPackageVEX + fields = ["state", "responses", "justification", "detail", "ignored"] + widgets = { + "detail": forms.Textarea(attrs={"rows": 3}), + } + + RESPONSES_VEX_CHOICES = [ + ("CNF", "CAN_NOT_FIX"), + ("RB", "ROLLBACK"), + ("U", "UPDATE"), + ("WNF", "WILL_NOT_FIX"), + ("WA", "WORKAROUND_AVAILABLE"), + ] + + responses = forms.MultipleChoiceField( + label="Response:", + choices=RESPONSES_VEX_CHOICES, + widget=forms.CheckboxSelectMultiple, + required=False, + ) + + helper = FormHelper() + helper.add_input(Submit("submit", "Submit", css_class="btn-success")) diff --git a/product_portfolio/migrations/0005_productpackagevex.py b/product_portfolio/migrations/0005_productpackagevex.py new file mode 100644 index 00000000..fa2e2643 --- /dev/null +++ b/product_portfolio/migrations/0005_productpackagevex.py @@ -0,0 +1,125 @@ +# Generated by Django 5.0.3 on 2024-05-04 15:51 + +import django.contrib.postgres.fields +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("dje", "0002_initial"), + ("product_portfolio", "0004_alter_scancodeproject_type"), + ] + + operations = [ + migrations.CreateModel( + name="ProductPackageVEX", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, editable=False, verbose_name="UUID" + ), + ), + ( + "vulnerability_id", + models.CharField(help_text="VCID-xxxx-xxxx-xxxx", max_length=50), + ), + ( + "state", + models.CharField( + choices=[ + ("R", "RESOLVED"), + ("RWP", "RESOLVED_WITH_PEDIGREE"), + ("E", "EXPLOITABLE"), + ("IT", "IN_TRIAGE"), + ("FP", "FALSE_POSITIVE"), + ("NA", "NOT_AFFECTED"), + ], + help_text="The rationale of why the impact analysis state was asserted.", + max_length=3, + ), + ), + ( + "responses", + django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + blank=True, + choices=[ + ("CNF", "CAN_NOT_FIX"), + ("RB", "ROLLBACK"), + ("U", "UPDATE"), + ("WNF", "WILL_NOT_FIX"), + ("WA", "WORKAROUND_AVAILABLE"), + ], + max_length=3, + ), + help_text="A response to the vulnerability by the manufacturer, supplier, or project responsible for the affected component or service", + max_length=20, + null=True, + size=None, + ), + ), + ( + "justification", + models.CharField( + choices=[ + ("CNP", "CODE_NOT_PRESENT"), + ("CNR", "CODE_NOT_REACHABLE"), + ("PP", "PROTECTED_AT_PERIMITER"), + ("PR", "PROTECTED_AT_RUNTIME"), + ("PC", "PROTECTED_BY_COMPILER"), + ("PMC", "PROTECTED_BY_MITIGATING_CONTROL"), + ("RC", "REQUIRES_CONFIGURATION"), + ("RD", "REQUIRES_DEPENDENCY"), + ("RE", "REQUIRES_ENVIRONMENT"), + ], + help_text="The rationale of why the impact analysis state was asserted.", + max_length=3, + ), + ), + ( + "detail", + models.CharField(help_text="Additional notes to explain the VEX."), + ), + ( + "ignored", + models.BooleanField( + default=False, help_text="Ignore VEX for this vulnerability" + ), + ), + ( + "dataspace", + models.ForeignKey( + editable=False, + help_text="A Dataspace is an independent, exclusive set of DejaCode data, which can be either nexB master reference data or installation-specific data.", + on_delete=django.db.models.deletion.PROTECT, + to="dje.dataspace", + ), + ), + ( + "productpackage", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="product_portfolio.productpackage", + ), + ), + ], + options={ + "unique_together": { + ("productpackage", "vulnerability_id", "dataspace") + }, + }, + ), + ] diff --git a/product_portfolio/models.py b/product_portfolio/models.py index e33d83f5..f95e9024 100644 --- a/product_portfolio/models.py +++ b/product_portfolio/models.py @@ -10,6 +10,7 @@ from pathlib import Path from django.conf import settings +from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.db import models from django.utils.functional import cached_property @@ -1247,3 +1248,93 @@ def notify(self, verb, description): recipient=self.created_by, description=description, ) + + +class ProductPackageVEX(DataspacedModel): + """ + Vulnerability Exploitability eXchange for + Single Product, Single Version, Single/* Vulnerability, Single Status + ( every Vulnerability and package should have a VEX ) + """ + + productpackage = models.ForeignKey(ProductPackage, on_delete=models.CASCADE) + vulnerability_id = models.CharField( + help_text="VCID-xxxx-xxxx-xxxx", max_length=50, null=False, blank=False + ) + + STATE_VEX_CHOICES = [ + ("R", "RESOLVED"), + ("RWP", "RESOLVED_WITH_PEDIGREE"), + ("E", "EXPLOITABLE"), + ("IT", "IN_TRIAGE"), + ("FP", "FALSE_POSITIVE"), + ("NA", "NOT_AFFECTED"), + ] + + state = models.CharField( + max_length=3, + help_text="The rationale of why the impact analysis state was asserted.", + choices=STATE_VEX_CHOICES, + ) + + RESPONSES_VEX_CHOICES = [ + ("CNF", "CAN_NOT_FIX"), + ("RB", "ROLLBACK"), + ("U", "UPDATE"), + ("WNF", "WILL_NOT_FIX"), + ("WA", "WORKAROUND_AVAILABLE"), + ] + + responses = ArrayField( + models.CharField( + max_length=3, + choices=RESPONSES_VEX_CHOICES, + blank=True, + ), + help_text="A response to the vulnerability by the manufacturer, " + "supplier, or project responsible for the affected component or service", + null=True, + max_length=20, + ) + + JUSTIFICATION_VEX_CHOICES = [ + ("CNP", "CODE_NOT_PRESENT"), + ("CNR", "CODE_NOT_REACHABLE"), + ("PP", "PROTECTED_AT_PERIMITER"), + ("PR", "PROTECTED_AT_RUNTIME"), + ("PC", "PROTECTED_BY_COMPILER"), + ("PMC", "PROTECTED_BY_MITIGATING_CONTROL"), + ("RC", "REQUIRES_CONFIGURATION"), + ("RD", "REQUIRES_DEPENDENCY"), + ("RE", "REQUIRES_ENVIRONMENT"), + ] + + justification = models.CharField( + max_length=3, + choices=JUSTIFICATION_VEX_CHOICES, + help_text="The rationale of why the impact analysis state was asserted.", + ) + + detail = models.CharField(help_text="Additional notes to explain the VEX.") + ignored = models.BooleanField(help_text="Ignore VEX for this vulnerability", default=False) + + class Meta: + unique_together = ("productpackage", "vulnerability_id", "dataspace") + + @property + def get_responses_choices(self): + labels = [] + for choice_code, choice_label in self.RESPONSES_VEX_CHOICES: + if choice_code in self.responses: + labels.append(choice_label) + return labels + + @property + def vulnerability_url(self): + return ( + f"{self.dataspace.configuration.vulnerablecode_url}" + f"vulnerabilities/{self.vulnerability_id}" + ) + + def __str__(self): + return f"{str(self.productpackage)} / {str(self.vulnerability_id)}" diff --git a/product_portfolio/templates/product_portfolio/product_details.html b/product_portfolio/templates/product_portfolio/product_details.html index bf375938..80a969ce 100644 --- a/product_portfolio/templates/product_portfolio/product_details.html +++ b/product_portfolio/templates/product_portfolio/product_details.html @@ -73,6 +73,12 @@ 1.5 1.4 + diff --git a/product_portfolio/templates/product_portfolio/tabs/tab_vex.html b/product_portfolio/templates/product_portfolio/tabs/tab_vex.html new file mode 100644 index 00000000..3612ece7 --- /dev/null +++ b/product_portfolio/templates/product_portfolio/tabs/tab_vex.html @@ -0,0 +1,58 @@ +{% load i18n %} +{% load as_icon from dje_tags %} +{% load urlize_target_blank from dje_tags %} +{% spaceless %} + + + + + + + + + + + + + + {% for vex in values.vex_items %} + {% cycle 'table-odd' '' as rowcolors silent %} + + + + {% include 'product_portfolio/includes/productrelation_element.html' with relation=vex.productpackage %} + + + + + + + + {% empty %} + + {% endfor %} + +
+ {% trans 'Package' %} + + {% trans 'Vulnerability' %} + + {% trans 'State' %} + + {% trans 'Responses' %} + + {% trans 'Justification' %} + + {% trans 'Detail' %} +
+ + + {{ vex.vulnerability_id }} + {{ vex.get_state_display }} +
    + {% for response in vex.get_responses_choices %} +
  • {{ response }}
  • + {% endfor %} +
+
{{ vex.get_justification_display }}{{ vex.detail }}
No results.
+{% endspaceless %} \ No newline at end of file diff --git a/product_portfolio/urls.py b/product_portfolio/urls.py index c63227c9..f4e73969 100644 --- a/product_portfolio/urls.py +++ b/product_portfolio/urls.py @@ -13,11 +13,13 @@ from product_portfolio.views import LoadSBOMsView from product_portfolio.views import ManageComponentGridView from product_portfolio.views import ManagePackageGridView +from product_portfolio.views import PackageVEXUpdateView from product_portfolio.views import ProductAddView from product_portfolio.views import ProductDeleteView from product_portfolio.views import ProductDetailsView from product_portfolio.views import ProductExportCycloneDXBOMView from product_portfolio.views import ProductExportSPDXDocumentView +from product_portfolio.views import ProductExportVEXView from product_portfolio.views import ProductListView from product_portfolio.views import ProductSendAboutFilesView from product_portfolio.views import ProductTabCodebaseView @@ -80,6 +82,7 @@ def product_path(path_segment, view): *product_path("about_files", ProductSendAboutFilesView.as_view()), *product_path("export_spdx", ProductExportSPDXDocumentView.as_view()), *product_path("export_cyclonedx", ProductExportCycloneDXBOMView.as_view()), + *product_path("export_vex", ProductExportVEXView.as_view()), *product_path("attribution", AttributionView.as_view()), *product_path("change", ProductUpdateView.as_view()), *product_path("delete", ProductDeleteView.as_view()), @@ -113,4 +116,14 @@ def product_path(path_segment, view): ProductListView.as_view(), name="product_list", ), + path( + "/productpackage///vex_change", + PackageVEXUpdateView.as_view(), + name="package_vex_change", + ), + path( + "", + ProductListView.as_view(), + name="productpackagevex_list", + ), ] diff --git a/product_portfolio/views.py b/product_portfolio/views.py index 2cd95eaf..ecdcbc6c 100644 --- a/product_portfolio/views.py +++ b/product_portfolio/views.py @@ -79,6 +79,7 @@ from dje.views import DataspaceScopeMixin from dje.views import ExportCycloneDXBOMView from dje.views import ExportSPDXDocumentView +from dje.views import ExportVEXView from dje.views import GetDataspacedObjectMixin from dje.views import Header from dje.views import LicenseDataForBuilderMixin @@ -101,6 +102,7 @@ from product_portfolio.forms import ImportFromScanForm from product_portfolio.forms import ImportManifestsForm from product_portfolio.forms import LoadSBOMsForm +from product_portfolio.forms import PackageVEXForm from product_portfolio.forms import ProductComponentForm from product_portfolio.forms import ProductComponentInlineForm from product_portfolio.forms import ProductCustomComponentForm @@ -114,6 +116,7 @@ from product_portfolio.models import Product from product_portfolio.models import ProductComponent from product_portfolio.models import ProductPackage +from product_portfolio.models import ProductPackageVEX from product_portfolio.models import ScanCodeProject @@ -281,6 +284,16 @@ class ProductDetailsView( "last_modified_by", ], }, + "vex": { + "fields": [ + "productpackage", + "vulnerability_id", + "state", + "responses", + "justification", + "detail", + ], + }, } def get_queryset(self): @@ -515,6 +528,22 @@ def tab_inventory(self): ], } + def tab_vex(self): + productpackage_qs = self.filter_productpackage.qs.order_by("feature", "package") + vex_items = ProductPackageVEX.objects.filter(productpackage__in=productpackage_qs) + label = f'VEX List {vex_items.count()}' + + tab_context = { + "vex_items": vex_items, + } + + return { + "label": format_html(label), + "fields": [ + (None, tab_context, None, "product_portfolio/tabs/tab_vex.html"), + ], + } + @staticmethod def inject_scan_data(scancodeio, feature_grouped, dataspace_uuid): download_urls = [ @@ -1431,6 +1460,10 @@ class ProductExportCycloneDXBOMView(BaseProductView, ExportCycloneDXBOMView): pass +class ProductExportVEXView(BaseProductView, ExportVEXView): + pass + + @login_required def scan_all_packages_view(request, dataspace, name, version=""): user = request.user @@ -1958,3 +1991,35 @@ def scancodeio_project_status_view(request, scancodeproject_uuid): } return TemplateResponse(request, template, context) + + +class PackageVEXUpdateView( + DataspacedUpdateView, +): + model = ProductPackageVEX + form_class = PackageVEXForm + permission_required = "component_catalog.change_package" + template_name = "component_catalog/package_vex_form.html" + slug_url_kwarg = ("vulnerability_id", "productpackage_id") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + opts = self.model._meta + context.update( + { + "verbose_name": opts.verbose_name, + "verbose_name_plural": opts.verbose_name_plural, + "list_url": reverse(f"{opts.app_label}:{opts.model_name}_list"), + } + ) + return context + + def get_success_url(self): + return reverse( + "product_portfolio:package_vex_change", + kwargs={ + "dataspace": self.kwargs["dataspace"], + "productpackage_id": self.kwargs["productpackage_id"], + "vulnerability_id": self.kwargs["vulnerability_id"], + }, + )