From 1bcd057abdf3fa6e29a3cc9b27b9a7e138a40d21 Mon Sep 17 00:00:00 2001 From: Paul Osinski Date: Fri, 26 Jun 2026 14:14:03 -0400 Subject: [PATCH 1/2] make impact_path deterministic --- .../jfrog_xray_api_summary_artifact/parser.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/dojo/tools/jfrog_xray_api_summary_artifact/parser.py b/dojo/tools/jfrog_xray_api_summary_artifact/parser.py index 2e8cba494a4..175af230e51 100644 --- a/dojo/tools/jfrog_xray_api_summary_artifact/parser.py +++ b/dojo/tools/jfrog_xray_api_summary_artifact/parser.py @@ -86,7 +86,10 @@ def get_item( with contextlib.suppress(CVSS3RHScoreDoesNotMatch, CVSS3RHMalformedError): cvssv3 = CVSS3.from_rh_vector(cvss_v3).clean_vector() - impact_paths = vulnerability.get("impact_path", []) + # JFrog returns impact_path in arbitrary order; sort so file_path, + # description, and unique_id stay stable across re-imports — otherwise + # one CVE flaps into multiple findings as the order changes between scans. + impact_paths = sorted(vulnerability.get("impact_path", [])) if len(impact_paths) > 0: impact_path = decode_impact_path(impact_paths[0]) @@ -112,6 +115,16 @@ def get_item( result.update(unique_id.encode()) unique_id_from_tool = result.hexdigest() + description = ( + impact_path.name + + ":" + + impact_path.version + + " -> " + + vulnerability["description"] + ) + if len(impact_paths) > 1: + description += "\n\n**Impact paths:**\n" + "\n".join(f"- {p}" for p in impact_paths) + finding = Finding( vuln_id_from_tool=vuln_id_from_tool, service=service, @@ -119,11 +132,7 @@ def get_item( cwe=cwe, cvssv3=cvssv3, severity=severity, - description=impact_path.name - + ":" - + impact_path.version - + " -> " - + vulnerability["description"], + description=description, test=test, file_path=impact_paths[0], component_name=artifact_name, From 3d71c2478c0dd373801e9c9915e8bc09b92ad9fb Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:44:53 -0600 Subject: [PATCH 2/2] docs: add 3.1 upgrade note for deterministic JFrog Xray impact paths The JFrog Xray API Summary Artifact parser now sorts impact paths before selecting the first one, so file_path, description, and unique_id_from_tool (the deduplication key) stay stable across re-imports. Previously JFrog's arbitrary impact_path ordering could change the dedup key between scans and cause a single CVE to be re-imported as multiple separate findings. Document the behavior change and cleanup guidance in the 3.1 upgrade notes. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/content/releases/os_upgrading/3.1.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/content/releases/os_upgrading/3.1.md b/docs/content/releases/os_upgrading/3.1.md index 6479832e42c..2a672f0eb2d 100644 --- a/docs/content/releases/os_upgrading/3.1.md +++ b/docs/content/releases/os_upgrading/3.1.md @@ -2,7 +2,7 @@ title: 'Upgrading to DefectDojo Version 3.1.x' toc_hide: true weight: -20260617 -description: Blank Finding components are now normalized to NULL so component-less findings group together; JIRA project configurations now support multiple comma-separated components; Tool Configuration credentials are re-encrypted to AES-256-GCM. +description: Blank Finding components are now normalized to NULL so component-less findings group together; JIRA project configurations now support multiple comma-separated components; Tool Configuration credentials are re-encrypted to AES-256-GCM; the JFrog Xray API Summary Artifact parser now sorts impact paths so findings deduplicate consistently across re-imports. --- ## Blank Finding components normalized to NULL @@ -37,4 +37,14 @@ This release also bumps `cryptography` to 49.0.0 and `pyopenssl` to 26.3.0. Nothing — the change is applied automatically by the database migration included in this release. Ensure your `DD_CREDENTIAL_AES_256_KEY` is unchanged from your prior deployment so the existing credentials can be decrypted and re-encrypted; a value that fails to decrypt (for example, because it was encrypted under a different key) is left untouched rather than overwritten. +## JFrog Xray API Summary Artifact parser: deterministic impact paths + +The JFrog Xray API Summary Artifact parser derives a finding's `file_path`, description, and `unique_id_from_tool` (its deduplication key) from the first entry in the vulnerability's `impact_path` list. JFrog returns that list in an arbitrary order, so the same vulnerability could produce a different first entry from one scan to the next. When the order changed, the deduplication key changed too, and a single CVE would be re-imported as multiple separate findings across successive scans. + +The parser now sorts the impact paths before selecting the first one, so `file_path`, the description, and the deduplication key stay stable across re-imports. When a vulnerability has more than one impact path, the full sorted list is now appended to the finding description under an **Impact paths:** heading so no path information is lost. + +### What you need to do + +Nothing is required for new imports. Findings imported before this release may have been created with a non-deterministic first impact path; re-importing an affected report will now deduplicate consistently, and you may want to clean up any duplicate findings that a previous import created. + For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/3.1.0).