From ba6ab380ffc16b6131819331383b4fff56dde66c Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Fri, 9 Jan 2026 13:10:16 +0100 Subject: [PATCH 1/3] :tada: Implement Cloudflare insights parser --- .../parsers/file/cloudflare_insights.md | 23 ++++ dojo/tools/cloudflare_insights/__init__.py | 0 dojo/tools/cloudflare_insights/parser.py | 128 ++++++++++++++++++ .../cloudflare_insights/many_findings.csv | 15 ++ .../scans/cloudflare_insights/one_finding.csv | 2 + .../tools/test_cloudflare_insights_parser.py | 24 ++++ 6 files changed, 192 insertions(+) create mode 100644 docs/content/supported_tools/parsers/file/cloudflare_insights.md create mode 100644 dojo/tools/cloudflare_insights/__init__.py create mode 100644 dojo/tools/cloudflare_insights/parser.py create mode 100644 unittests/scans/cloudflare_insights/many_findings.csv create mode 100644 unittests/scans/cloudflare_insights/one_finding.csv create mode 100644 unittests/tools/test_cloudflare_insights_parser.py diff --git a/docs/content/supported_tools/parsers/file/cloudflare_insights.md b/docs/content/supported_tools/parsers/file/cloudflare_insights.md new file mode 100644 index 00000000000..619b5573f9f --- /dev/null +++ b/docs/content/supported_tools/parsers/file/cloudflare_insights.md @@ -0,0 +1,23 @@ + +--- +title: "Cloudflare Insights" +toc_hide: true +--- + +Import Cloudflare Insights findings using the **CSV export** provided by Cloudflare. + +### Sample Scan Data +Sample Cloudflare Insights files can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/cloudflare_insights). + +### Supported Fields +The parser supports the following CSV columns: + +- `severity` +- `issue_class` +- `subject` +- `issue_type` +- `status` +- `insight` *(optional)* +- `detection_method` *(optional)* +- `risk` *(optional)* +- `recommended_action` diff --git a/dojo/tools/cloudflare_insights/__init__.py b/dojo/tools/cloudflare_insights/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/tools/cloudflare_insights/parser.py b/dojo/tools/cloudflare_insights/parser.py new file mode 100644 index 00000000000..ded2dea2538 --- /dev/null +++ b/dojo/tools/cloudflare_insights/parser.py @@ -0,0 +1,128 @@ +import csv +import io +from urllib.parse import urlparse + +from dojo.models import Endpoint, Finding + + +class CloudflareInsightsParser: + + """ + DefectDojo parser for Cloudflare Insights CSV exports. + + Expected columns: + - severity + - issue_class + - subject (used as Endpoint host; not repeated in description) + - issue_type + - scan_performed_on (ignored) + - status + - insight (optional) + - detection_method (optional) + - risk (optional) + - recommended_action (used as mitigation if present) + """ + + def get_scan_types(self): + return ["Cloudflare Insights"] + + def get_label_for_scan_types(self, scan_type): + return scan_type + + def get_description_for_scan_types(self, scan_type): + return "Import Cloudflare Insights (CSV export)." + + def _map_severity(self, value): + normalized = value.strip().lower() + mapping = { + "low": "Low", + "moderate": "Medium", + "critical": "Critical", + "high": "High", # optional: Cloudflare occasionally uses this + } + return mapping.get(normalized, "Info") + + def _extract_host_from_subject(self, subject: str) -> str | None: + if not subject: + return None + s = subject.strip() + if not s: + return None + parsed = urlparse(s) + netloc = parsed.netloc + if not netloc and ("." in s or ":" in s or s.startswith("localhost")): + parsed2 = urlparse(f"http://{s}") + netloc = parsed2.netloc + host = netloc or s + if ":" in host: + host = host.split(":", 1)[0] + host = host.strip().strip("/").strip() + + return host or None + + def _is_inactive_status(self, status: str) -> bool: + inactive_markers = {"resolved", "mitigated", "closed", "fixed"} + return bool(status) and status.strip().lower() in inactive_markers + + def get_findings(self, filename, test): + content = filename.read() + if isinstance(content, bytes): + content = content.decode("utf-8", errors="replace") + + reader = csv.DictReader( + io.StringIO(content), + delimiter=",", + quotechar='"', + skipinitialspace=True, + ) + findings = [] + for row in reader: + severity_raw = (row.get("severity") or "").strip() + issue_class = (row.get("issue_class") or "").strip() + subject = (row.get("subject") or "").strip() + issue_type = (row.get("issue_type") or "").strip() + status = (row.get("status") or "").strip() + insight = (row.get("insight") or "").strip() + detection_method = (row.get("detection_method") or "").strip() + risk = (row.get("risk") or "").strip() + recommended_action = (row.get("recommended_action") or "").strip() + mapped_severity = self._map_severity(severity_raw) + if issue_type and subject: + title = f"{issue_type}: {subject}" + elif issue_type: + title = issue_type + elif subject: + title = subject + else: + title = "Cloudflare Insight" + description_parts = [] + if issue_class: + description_parts.append(f"**Issue class**: {issue_class}") + if issue_type: + description_parts.append(f"**Issue type**: {issue_type}") + if status: + description_parts.append(f"**Status**: {status}") + if insight: + description_parts.append(f"**Insight**: {insight}") + if detection_method: + description_parts.append(f"**Detection method**: {detection_method}") + if risk: + description_parts.append(f"**Risk**: {risk}") + description = "\n\n".join(description_parts) + finding = Finding( + test=test, + title=title, + severity=mapped_severity, + description=description, + mitigation=recommended_action or "Not provided!", + references="Not provided!", + static_finding=False, + dynamic_finding=True, + ) + finding.active = not self._is_inactive_status(status) + host = self._extract_host_from_subject(subject) + if host: + finding.unsaved_endpoints = [Endpoint(host=host, port=None)] + findings.append(finding) + + return findings diff --git a/unittests/scans/cloudflare_insights/many_findings.csv b/unittests/scans/cloudflare_insights/many_findings.csv new file mode 100644 index 00000000000..48a00cbbcd0 --- /dev/null +++ b/unittests/scans/cloudflare_insights/many_findings.csv @@ -0,0 +1,15 @@ +severity,issue_class,subject,issue_type,scan_performed_on,status,insight,detection_method,risk,recommended_action +Moderate,Unproxied 'A' Records,domain1.com,Exposed infrastructure,2024-07-05T05:30:57.976844Z,Active,,,,"Configure Cloudflare to proxy the DNS record. By setting up Cloudflare as your hostname's reverse proxy, Cloudflare protects origin servers from DDoS attacks by hiding their IP addresses. You can configure Cloudflare to proxy your hostname in your DNS settings." +Moderate,Unproxied 'A' Records,domain2.com,Exposed infrastructure,2024-07-05T05:31:39.692808Z,Active,,,,"Configure Cloudflare to proxy the DNS record. By setting up Cloudflare as your hostname's reverse proxy, Cloudflare protects origin servers from DDoS attacks by hiding their IP addresses. You can configure Cloudflare to proxy your hostname in your DNS settings." +Low,Security.txt not configured,domain3.com,Configuration suggestion,2024-12-01T05:43:45.712676Z,Active,Security.txt not configured. Configure and manage the Security.txt file to improve the website's vulnerability disclosure process,We evaluated the Security Settings configured for this domain and found that Security.txt is not enabled.,"The absence of Security.txt insights creates a lack of a clear, accessible method for researchers to report vulnerabilities. This can lead to security issues going unnoticed or under-reported, increasing the risk of exploitation.","Configure Security.txt file. " +Low,Security.txt not configured,domain4.com,Configuration suggestion,2024-12-01T05:43:44.252529Z,Active,Security.txt not configured. Configure and manage the Security.txt file to improve the website's vulnerability disclosure process,We evaluated the Security Settings configured for this domain and found that Security.txt is not enabled.,"The absence of Security.txt insights creates a lack of a clear, accessible method for researchers to report vulnerabilities. This can lead to security issues going unnoticed or under-reported, increasing the risk of exploitation.","Configure Security.txt file. " +Moderate,Unproxied CNAME Records,domain5.com,Exposed infrastructure,2024-07-08T03:37:16.031911Z,Active,,,,"Configure Cloudflare to proxy the DNS record. By setting up Cloudflare as your hostname's reverse proxy, Cloudflare protects origin servers from DDoS attacks by hiding their IP addresses. You can configure Cloudflare to proxy your hostname in your DNS settings." +Moderate,Unproxied 'A' Records,domain6.com,Exposed infrastructure,2024-07-02T12:55:57.798974Z,Active,,,,"Configure Cloudflare to proxy the DNS record. By setting up Cloudflare as your hostname's reverse proxy, Cloudflare protects origin servers from DDoS attacks by hiding their IP addresses. You can configure Cloudflare to proxy your hostname in your DNS settings." +Low,Security.txt not configured,domain7.com,Configuration suggestion,2025-03-25T17:33:40.070204Z,Active,Security.txt not configured. Configure and manage the Security.txt file to improve the website's vulnerability disclosure process,We evaluated the Security Settings configured for this domain and found that Security.txt is not enabled.,"The absence of Security.txt insights creates a lack of a clear, accessible method for researchers to report vulnerabilities. This can lead to security issues going unnoticed or under-reported, increasing the risk of exploitation.","Configure Security.txt file. " +Low,Security.txt not configured,domain8.com,Configuration suggestion,2025-03-25T17:33:41.970652Z,Active,Security.txt not configured. Configure and manage the Security.txt file to improve the website's vulnerability disclosure process,We evaluated the Security Settings configured for this domain and found that Security.txt is not enabled.,"The absence of Security.txt insights creates a lack of a clear, accessible method for researchers to report vulnerabilities. This can lead to security issues going unnoticed or under-reported, increasing the risk of exploitation.","Configure Security.txt file. " +Moderate,Unproxied 'A' Records,domain9.com,Exposed infrastructure,2024-07-05T05:30:46.435059Z,Active,,,,"Configure Cloudflare to proxy the DNS record. By setting up Cloudflare as your hostname's reverse proxy, Cloudflare protects origin servers from DDoS attacks by hiding their IP addresses. You can configure Cloudflare to proxy your hostname in your DNS settings." +Low,Security.txt not configured,domain10.com,Configuration suggestion,2024-11-29T05:32:39.671608Z,Active,Security.txt not configured. Configure and manage the Security.txt file to improve the website's vulnerability disclosure process,We evaluated the Security Settings configured for this domain and found that Security.txt is not enabled.,"The absence of Security.txt insights creates a lack of a clear, accessible method for researchers to report vulnerabilities. This can lead to security issues going unnoticed or under-reported, increasing the risk of exploitation.","Configure Security.txt file. " +Low,Security.txt not configured,domain11.com,Configuration suggestion,2025-03-06T15:16:53.931468Z,Active,Security.txt not configured. Configure and manage the Security.txt file to improve the website's vulnerability disclosure process,We evaluated the Security Settings configured for this domain and found that Security.txt is not enabled.,"The absence of Security.txt insights creates a lack of a clear, accessible method for researchers to report vulnerabilities. This can lead to security issues going unnoticed or under-reported, increasing the risk of exploitation.","Configure Security.txt file. " +Moderate,Unproxied CNAME Records,domain12.com,Exposed infrastructure,2026-01-02T12:29:43.13416Z,Active,Unproxied CNAME Records. This DNS record is not proxied by Cloudflare. Your origin server is directly exposed and has a higher risk of a DDoS attack.,We reviewed your Cloudflare DNS settings and checked whether your hostname accepts connections on either port 80 or 443.,DDoS Attack,"Configure Cloudflare to proxy the DNS record. By setting up Cloudflare as your hostname's reverse proxy, Cloudflare protects origin servers from DDoS attacks by hiding their IP addresses. You can configure Cloudflare to proxy your hostname in your DNS settings." +Moderate,Unproxied 'A' Records,domain13.com,Exposed infrastructure,2024-07-02T12:57:30.878124Z,Active,,,,"Configure Cloudflare to proxy the DNS record. By setting up Cloudflare as your hostname's reverse proxy, Cloudflare protects origin servers from DDoS attacks by hiding their IP addresses. You can configure Cloudflare to proxy your hostname in your DNS settings." +Critical,Managed Rules not deployed,domain14.com,Configuration suggestion,2024-07-01T17:44:27.896818Z,Active,Managed Rules not deployed. We have detected that you have not enabled the Cloudflare Managed Rules feature on your zone.,We evaluated your websites and you have no Managed Rules deployed.,Insufficient protection for vulnerabilities targeting Web and API applications,Turn on Managed Rules. Deploy Cloudflare Managed Rules on your zone to protect your web application against common vulnerabilities in web applications. \ No newline at end of file diff --git a/unittests/scans/cloudflare_insights/one_finding.csv b/unittests/scans/cloudflare_insights/one_finding.csv new file mode 100644 index 00000000000..9d376771de0 --- /dev/null +++ b/unittests/scans/cloudflare_insights/one_finding.csv @@ -0,0 +1,2 @@ +severity,issue_class,subject,issue_type,scan_performed_on,status,insight,detection_method,risk,recommended_action +Moderate,Unproxied 'A' Records,domain.com,Exposed infrastructure,2024-07-06T13:50:15.536086Z,Active,,,,"Configure Cloudflare to proxy the DNS record. By setting up Cloudflare as your hostname's reverse proxy, Cloudflare protects origin servers from DDoS attacks by hiding their IP addresses. You can configure Cloudflare to proxy your hostname in your DNS settings." \ No newline at end of file diff --git a/unittests/tools/test_cloudflare_insights_parser.py b/unittests/tools/test_cloudflare_insights_parser.py new file mode 100644 index 00000000000..8d48280df82 --- /dev/null +++ b/unittests/tools/test_cloudflare_insights_parser.py @@ -0,0 +1,24 @@ +from dojo.models import Test +from dojo.tools.cloudflare_insights.parser import CloudflareInsightsParser +from unittests.dojo_test_case import DojoTestCase, get_unit_tests_scans_path + + +class TestCloudflareInsightsParser(DojoTestCase): + + def test_cloudflare_insights_parser_with_one_finding(self): + with (get_unit_tests_scans_path("cloudflare_insights") / "one_finding.csv").open(encoding="utf-8") as testfile: + parser = CloudflareInsightsParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(1, len(findings)) + finding = findings[0] + self.assertEqual("Exposed infrastructure: domain.com", finding.title) + self.assertEqual("Medium", finding.severity) + + def test_cloudflare_insights_parser_with_many_findings(self): + with (get_unit_tests_scans_path("cloudflare_insights") / "many_findings.csv").open(encoding="utf-8") as testfile: + parser = CloudflareInsightsParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(14, len(findings)) + finding = findings[0] + self.assertEqual("Exposed infrastructure: domain1.com", finding.title) + self.assertEqual("Medium", finding.severity) From 43715ab311c16e97e7688a2f81979f9abb886ddf Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Fri, 9 Jan 2026 13:12:16 +0100 Subject: [PATCH 2/3] . --- docs/content/supported_tools/parsers/file/cloudflare_insights.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/content/supported_tools/parsers/file/cloudflare_insights.md b/docs/content/supported_tools/parsers/file/cloudflare_insights.md index 619b5573f9f..035ed4f7033 100644 --- a/docs/content/supported_tools/parsers/file/cloudflare_insights.md +++ b/docs/content/supported_tools/parsers/file/cloudflare_insights.md @@ -1,4 +1,3 @@ - --- title: "Cloudflare Insights" toc_hide: true From bc3d13b0c0286bf54e38131bc543174f2e06f2d0 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Sun, 11 Jan 2026 19:50:45 +0100 Subject: [PATCH 3/3] Update dojo/tools/cloudflare_insights/parser.py Co-authored-by: valentijnscholten --- dojo/tools/cloudflare_insights/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/tools/cloudflare_insights/parser.py b/dojo/tools/cloudflare_insights/parser.py index ded2dea2538..e47065b1e61 100644 --- a/dojo/tools/cloudflare_insights/parser.py +++ b/dojo/tools/cloudflare_insights/parser.py @@ -114,7 +114,7 @@ def get_findings(self, filename, test): title=title, severity=mapped_severity, description=description, - mitigation=recommended_action or "Not provided!", + mitigation=recommended_action, references="Not provided!", static_finding=False, dynamic_finding=True,