From a47b1085341f8dce6e864c91c56d25fa7073070b Mon Sep 17 00:00:00 2001 From: Goldloli <39011904+Goldloli@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:33:22 +0800 Subject: [PATCH] fix: skip Qualys XML findings whose QID has no glossary entry --- dojo/tools/qualys/parser.py | 5 +++++ unittests/tools/test_qualys_parser.py | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/dojo/tools/qualys/parser.py b/dojo/tools/qualys/parser.py index 465301310f4..8eaa43e8c82 100644 --- a/dojo/tools/qualys/parser.py +++ b/dojo/tools/qualys/parser.py @@ -267,6 +267,11 @@ def parse_finding(host, tree): search = f".//GLOSSARY/VULN_DETAILS_LIST/VULN_DETAILS[@id='{gid}']" vuln_item = tree.find(search) + if vuln_item is None: + # A finding whose QID has no GLOSSARY entry has neither severity nor title to + # import, so skip it with a warning instead of aborting the whole import. + logger.warning("Qualys report has no GLOSSARY entry for QID %s, skipping its finding", gid) + continue if vuln_item is not None: finding = Finding() # Vuln name diff --git a/unittests/tools/test_qualys_parser.py b/unittests/tools/test_qualys_parser.py index 060b6b9fcc0..9ffd8732ed7 100644 --- a/unittests/tools/test_qualys_parser.py +++ b/unittests/tools/test_qualys_parser.py @@ -1,4 +1,6 @@ import datetime +import io +import re from django.test import override_settings @@ -87,6 +89,31 @@ def parse_file_with_no_vuln_has_no_findings_csv(self): self.validate_locations(findings) self.assertEqual(0, len(findings)) + def test_finding_with_missing_glossary_entry_is_skipped(self): + # A QID that matches no GLOSSARY VULN_DETAILS has neither severity nor title to + # import; the parser used to raise AttributeError on it and abort the whole import. + sample = (get_unit_tests_scans_path("qualys") / "Qualys_Sample_Report.xml").read_text(encoding="utf-8") + without_qid_6_entry = re.sub( + r".*?", + "", + sample, + count=1, + flags=re.DOTALL, + ) + report = io.BytesIO(without_qid_6_entry.encode()) + report.name = "report.xml" + + with self.assertLogs("dojo.tools.qualys.parser", level="WARNING") as logged: + findings = QualysParser().get_findings(report, Test()) + + # Seven findings report QID 6 in the full sample's 301; each is skipped + # while everything else still imports. + self.validate_locations(findings) + self.assertEqual(294, len(findings)) + titles = {finding.title for finding in findings} + self.assertNotIn("QID-6 | DNS Host Name", titles) + self.assertTrue(any("qid_6" in message for message in logged.output)) + @override_settings(USE_FIRST_SEEN=True) def test_parse_file_with_multiple_vuln_has_multiple_findings_csv_first_seen(self): finding = self.parse_file_with_multiple_vuln_has_multiple_findings_csv()