From a896bd06cda0ddfdbc7464681a4e8b11725e1249 Mon Sep 17 00:00:00 2001 From: Andre Schlegel-Tylla Date: Thu, 12 Mar 2026 14:46:18 +0100 Subject: [PATCH 1/2] Support CVSS4 and also import CVSS vectors, references and publish date. --- dojo/tools/dependency_track/parser.py | 182 ++++++++++++------ .../scans/dependency_track/one_finding.json | 6 +- .../tools/test_dependency_track_parser.py | 13 ++ 3 files changed, 140 insertions(+), 61 deletions(-) diff --git a/dojo/tools/dependency_track/parser.py b/dojo/tools/dependency_track/parser.py index 657806c5c8c..c9d97a6e7a0 100644 --- a/dojo/tools/dependency_track/parser.py +++ b/dojo/tools/dependency_track/parser.py @@ -1,10 +1,13 @@ import json import logging +from dateutil import parser + from django.conf import settings from dojo.models import Finding from dojo.tools.locations import LocationData +from dojo.utils import parse_cvss_data logger = logging.getLogger(__name__) @@ -19,71 +22,95 @@ class DependencyTrackParser: A typical Finding Packaging Format (FPF) export looks like the following: { + "version": "1.3", + "meta" : { + "application": "Dependency-Track", + "version": "4.5.0", + "timestamp": "2022-02-18T23:31:42Z", + "baseUrl": "http://dtrack.example.org" + }, + "project" : { + "uuid": "ca4f2da9-0fad-4a13-92d7-f627f3168a56", + "name": "Acme Example", "version": "1.0", - "meta" : { - "application": "Dependency-Track", - "version": "3.4.0", - "timestamp": "2018-11-18T23:31:42Z", - "baseUrl": "http://dtrack.example.org" + "description": "A sample application" + }, + "findings" : [ + { + "component": { + "uuid": "b815b581-fec1-4374-a871-68862a8f8d52", + "name": "timespan", + "version": "2.3.0", + "purl": "pkg:npm/timespan@2.3.0", + "latestVersion": "3.2.0" + }, + "vulnerability": { + "uuid": "115b80bb-46c4-41d1-9f10-8a175d4abb46", + "source": "NPM", + "vulnId": "533", + "title": "Regular Expression Denial of Service", + "subtitle": "timespan", + "severity": "LOW", + "severityRank": 3, + "cvssV2Vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P", + "cvssV3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "cvssV4Vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N", + "references": "* [https://example.com](https://example.com)\n* [https://example.org](https://example.org)", + "published": "2025-07-11 03:16:03.563", + "cweId": 400, + "cweName": "Uncontrolled Resource Consumption ('Resource Exhaustion')", + "cwes": [ + { + "cweId": 400, + "name": "Uncontrolled Resource Consumption ('Resource Exhaustion')" + } + ], + "description": "Affected versions of `timespan`...", + "recommendation": "No direct patch is available..." }, - "project" : { - "uuid": "ca4f2da9-0fad-4a13-92d7-f627f3168a56", - "name": "Acme Example", - "version": "1.0", - "description": "A sample application" + "analysis": { + "state": "NOT_SET", + "isSuppressed": false }, - "findings" : [ + "matrix": "ca4f2da9-0fad-4a13-92d7-f627f3168a56:b815b581-fec1-4374-a871-68862a8f8d52:115b80bb-46c4-41d1-9f10-8a175d4abb46" + }, + { + "component": { + "uuid": "979f87f5-eaf5-4095-9d38-cde17bf9228e", + "name": "uglify-js", + "version": "2.4.24", + "purl": "pkg:npm/uglify-js@2.4.24" + }, + "vulnerability": { + "uuid": "701a3953-666b-4b7a-96ca-e1e6a3e1def3", + "source": "NPM", + "vulnId": "48", + "aliases": [ { - "component": { - "uuid": "b815b581-fec1-4374-a871-68862a8f8d52", - "name": "timespan", - "version": "2.3.0", - "purl": "pkg:npm/timespan@2.3.0" - }, - "vulnerability": { - "uuid": "115b80bb-46c4-41d1-9f10-8a175d4abb46", - "source": "NPM", - "vulnId": "533", - "title": "Regular Expression Denial of Service", - "subtitle": "timespan", - "severity": "LOW", - "severityRank": 3, - "cweId": 400, - "cweName": "Uncontrolled Resource Consumption ('Resource Exhaustion')", - "description": "Affected versions of `timespan`...", - "recommendation": "No direct patch is available..." - }, - "analysis": { - "state": "NOT_SET", - "isSuppressed": false - }, - "matrix": "ca4f2da9-0fad-4a13-92d7-f627f3168a56:b815b581-fec1-4374-a871-68862a8f8d52:115b80bb-46c4-41d1-9f10-8a175d4abb46" - }, + "cveId": "CVE-2022-2053", + "ghsaId": "GHSA-95rf-557x-44g5" + } + ], + "title": "Regular Expression Denial of Service", + "subtitle": "uglify-js", + "severity": "LOW", + "severityRank": 3, + "cweId": 400, + "cweName": "Uncontrolled Resource Consumption ('Resource Exhaustion')", + "cwes": [ { - "component": { - "uuid": "979f87f5-eaf5-4095-9d38-cde17bf9228e", - "name": "uglify-js", - "version": "2.4.24", - "purl": "pkg:npm/uglify-js@2.4.24" - }, - "vulnerability": { - "uuid": "701a3953-666b-4b7a-96ca-e1e6a3e1def3", - "source": "NPM", - "vulnId": "48", - "title": "Regular Expression Denial of Service", - "subtitle": "uglify-js", - "severity": "LOW", - "severityRank": 3, - "cweId": 400, - "cweName": "Uncontrolled Resource Consumption ('Resource Exhaustion')", - "description": "Versions of `uglify-js` prior to...", - "recommendation": "Update to version 2.6.0 or later." - }, - "analysis": { - "isSuppressed": false - }, - "matrix": "ca4f2da9-0fad-4a13-92d7-f627f3168a56:979f87f5-eaf5-4095-9d38-cde17bf9228e:701a3953-666b-4b7a-96ca-e1e6a3e1def3" - }] + "cweId": 400, + "name": "Uncontrolled Resource Consumption ('Resource Exhaustion')" + } + ], + "description": "Versions of `uglify-js` prior to...", + "recommendation": "Update to version 2.6.0 or later." + }, + "analysis": { + "isSuppressed": false + }, + "matrix": "ca4f2da9-0fad-4a13-92d7-f627f3168a56:979f87f5-eaf5-4095-9d38-cde17bf9228e:701a3953-666b-4b7a-96ca-e1e6a3e1def3" + }] } """ @@ -216,6 +243,23 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin # Get the cvss score of the vulnerabililty cvss_score = dependency_track_finding["vulnerability"].get("cvssV3BaseScore") + cvssv3 = None + if "cvssV3Vector" in dependency_track_finding["vulnerability"]: + cvss_vector = dependency_track_finding["vulnerability"]["cvssV3Vector"] + cvss_data = parse_cvss_data(cvss_vector) + if cvss_data: + cvssv3 = cvss_data.get("cvssv3") + cvss_score = cvss_data.get("cvssv3_score") + + cvssv4 = None + cvssv4_score = None + if "cvssV4Vector" in dependency_track_finding["vulnerability"]: + cvss_vector = dependency_track_finding["vulnerability"]["cvssV4Vector"] + cvss_data = parse_cvss_data(cvss_vector) + if cvss_data: + cvssv4 = cvss_data.get("cvssv4") + cvssv4_score = cvss_data.get("cvssv4_score") + # Use the analysis state from Dependency Track to determine if the finding has already been marked as a false positive upstream analysis = dependency_track_finding.get("analysis") is_false_positive = bool(analysis is not None and analysis.get("state") == "FALSE_POSITIVE") @@ -225,6 +269,13 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin epss_score = dependency_track_finding["vulnerability"].get("epssScore", None) + references = dependency_track_finding["vulnerability"].get("references") + if references: + if isinstance(references, list): + references = "\n".join(references) + + published = dependency_track_finding["vulnerability"].get("published") + # Build and return Finding model finding = Finding( title=title, @@ -238,6 +289,7 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin file_path=file_path, unique_id_from_tool=unique_id_from_tool, vuln_id_from_tool=vuln_id_from_tool, + references=references, static_finding=True, dynamic_finding=False) @@ -250,6 +302,16 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin if cvss_score: finding.cvssv3_score = cvss_score + if cvssv3: + finding.cvssv3 = cvssv3 + + if cvssv4_score: + finding.cvssv4_score = cvssv4_score + if cvssv4: + finding.cvssv4 = cvssv4 + + if published: + finding.publish_date = parser.parse(published).date() if epss_score: finding.epss_score = epss_score diff --git a/unittests/scans/dependency_track/one_finding.json b/unittests/scans/dependency_track/one_finding.json index 8ed4925a664..1bd90e044f6 100644 --- a/unittests/scans/dependency_track/one_finding.json +++ b/unittests/scans/dependency_track/one_finding.json @@ -31,7 +31,11 @@ "cweId": 400, "cweName": "Uncontrolled Resource Consumption ('Resource Exhaustion')", "description": "Affected versions of `timespan`...", - "recommendation": "No direct patch is available..." + "recommendation": "No direct patch is available...", + "cvssV3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "cvssV4Vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N", + "references": "* [https://example.com](https://example.com)\n* [https://example.org](https://example.org)", + "published": "2025-07-11 03:16:03.563" }, "analysis": { "state": "NOT_SET", diff --git a/unittests/tools/test_dependency_track_parser.py b/unittests/tools/test_dependency_track_parser.py index b4fb2156af5..7f9bb6945e2 100644 --- a/unittests/tools/test_dependency_track_parser.py +++ b/unittests/tools/test_dependency_track_parser.py @@ -1,3 +1,5 @@ +from datetime import date + from dojo.models import Test from dojo.tools.dependency_track.parser import DependencyTrackParser from unittests.dojo_test_case import DojoTestCase, get_unit_tests_scans_path @@ -60,6 +62,17 @@ def test_dependency_track_parser_has_one_finding(self): "ca4f2da9-0fad-4a13-92d7-f627f3168a56:b815b581-fec1-4374-a871-68862a8f8d52:115b80bb-46c4-41d1-9f10-8a175d4abb46", findings[0].unique_id_from_tool, ) + self.assertEqual( + "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + findings[0].cvssv3, + ) + self.assertEqual( + "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N", + findings[0].cvssv4, + ) + self.assertIn("https://example.com", findings[0].references) + self.assertIn("https://example.org", findings[0].references) + self.assertEqual(date(2025,7,11), findings[0].publish_date) def test_dependency_track_parser_v3_8_0(self): with ( From e7d013cada7303407fcfd1e85a9a51b8921d75f5 Mon Sep 17 00:00:00 2001 From: Andre Schlegel-Tylla Date: Fri, 13 Mar 2026 05:47:45 +0100 Subject: [PATCH 2/2] Fix linter issues --- dojo/tools/dependency_track/parser.py | 3 +-- unittests/tools/test_dependency_track_parser.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dojo/tools/dependency_track/parser.py b/dojo/tools/dependency_track/parser.py index c9d97a6e7a0..3327559e2f8 100644 --- a/dojo/tools/dependency_track/parser.py +++ b/dojo/tools/dependency_track/parser.py @@ -2,7 +2,6 @@ import logging from dateutil import parser - from django.conf import settings from dojo.models import Finding @@ -14,7 +13,7 @@ class DependencyTrackParser: - """ + r""" A class that can be used to parse the JSON Finding Packaging Format (FPF) export from OWASP Dependency Track. See here for more info on this JSON format: https://docs.dependencytrack.org/integrations/file-formats/ diff --git a/unittests/tools/test_dependency_track_parser.py b/unittests/tools/test_dependency_track_parser.py index 7f9bb6945e2..126051864b7 100644 --- a/unittests/tools/test_dependency_track_parser.py +++ b/unittests/tools/test_dependency_track_parser.py @@ -72,7 +72,7 @@ def test_dependency_track_parser_has_one_finding(self): ) self.assertIn("https://example.com", findings[0].references) self.assertIn("https://example.org", findings[0].references) - self.assertEqual(date(2025,7,11), findings[0].publish_date) + self.assertEqual(date(2025, 7, 11), findings[0].publish_date) def test_dependency_track_parser_v3_8_0(self): with (