diff --git a/dojo/tools/qualys/parser.py b/dojo/tools/qualys/parser.py index ad1072a7d32..c8e3cc53778 100644 --- a/dojo/tools/qualys/parser.py +++ b/dojo/tools/qualys/parser.py @@ -266,6 +266,11 @@ def parse_finding(host, glossary): temp["CVSS_vector"] = None vuln_item = glossary.get(gid) + 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 814b6ae12fe..a4efaa049a4 100644 --- a/unittests/tools/test_qualys_parser.py +++ b/unittests/tools/test_qualys_parser.py @@ -146,6 +146,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()