Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dojo/tools/qualys/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions unittests/tools/test_qualys_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"<VULN_DETAILS id=\"qid_6\">.*?</VULN_DETAILS>",
"",
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()
Expand Down
Loading