From 9fe0c5e4ede5aa930aac1f9dda19bed36ac21fbe Mon Sep 17 00:00:00 2001 From: renejal Date: Tue, 17 Oct 2023 17:47:42 -0500 Subject: [PATCH 1/5] add new parser xray --- .../file/Jfrog_xray_on_demand_binary Scan.md | 9 + dojo/fixtures/defect_dojo_sample_data.json | 10 + dojo/settings/settings.dist.py | 2 + .../__init__.py | 0 .../parser.py | 224 ++++++++++++++++++ .../many_vulns.json | 111 +++++++++ .../one_vuln.json | 43 ++++ ...jfrog_xray_on_demand_binary_scan_parser.py | 33 +++ 8 files changed, 432 insertions(+) create mode 100644 docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md create mode 100644 dojo/tools/jfrog_xray_on_demand_binary_scan/__init__.py create mode 100644 dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py create mode 100644 unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json create mode 100644 unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json create mode 100644 unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py diff --git a/docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md b/docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md new file mode 100644 index 00000000000..d34f88fdf2a --- /dev/null +++ b/docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md @@ -0,0 +1,9 @@ +--- +title: "Jfrog xray on demand binary Scan" +toc_hide: true +--- +Import the JSON format for the \"Jfrog xray on demand binary Scan\" file. Use this importer for Xray version 2.X +-- + jfron file documentation + +https://jfrog.com/help/r/jfrog-cli/on-demand-binary-scan diff --git a/dojo/fixtures/defect_dojo_sample_data.json b/dojo/fixtures/defect_dojo_sample_data.json index 3db55c5d9d2..14ce0dca4fe 100644 --- a/dojo/fixtures/defect_dojo_sample_data.json +++ b/dojo/fixtures/defect_dojo_sample_data.json @@ -8620,6 +8620,16 @@ } }, { + "model": "dojo.test_type", + "pk": 149, + "fields": { + "name": "Jfrog Xray On Demand Binary Scan", + "static_tool": false, + "dynamic_tool": false, + "active": true + } + }, + { "model": "dojo.tagulous_product_tags", "pk": 1, "fields": { diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 82d4303a941..6abc2b6cdbe 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1223,6 +1223,7 @@ def saml2_attrib_map_format(dict): 'GitLab Dependency Scanning Report': ['title', 'vulnerability_ids', 'file_path', 'component_name', 'component_version'], 'SpotBugs Scan': ['cwe', 'severity', 'file_path', 'line'], 'JFrog Xray Unified Scan': ['vulnerability_ids', 'file_path', 'component_name', 'component_version'], + 'Jfrog Xray On Demand Binary Scan': ["title", "description", "component_name", "component_version"], 'Scout Suite Scan': ['file_path', 'vuln_id_from_tool'], # for now we use file_path as there is no attribute for "service" 'AWS Security Hub Scan': ['unique_id_from_tool'], 'Meterian Scan': ['cwe', 'component_name', 'component_version', 'description', 'severity'], @@ -1411,6 +1412,7 @@ def saml2_attrib_map_format(dict): 'Checkov Scan': DEDUPE_ALGO_HASH_CODE, 'SpotBugs Scan': DEDUPE_ALGO_HASH_CODE, 'JFrog Xray Unified Scan': DEDUPE_ALGO_HASH_CODE, + 'Jfrog Xray On Demand Binary Scan': DEDUPE_ALGO_HASH_CODE, 'Scout Suite Scan': DEDUPE_ALGO_HASH_CODE, 'AWS Security Hub Scan': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, 'Meterian Scan': DEDUPE_ALGO_HASH_CODE, diff --git a/dojo/tools/jfrog_xray_on_demand_binary_scan/__init__.py b/dojo/tools/jfrog_xray_on_demand_binary_scan/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py b/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py new file mode 100644 index 00000000000..8157851d151 --- /dev/null +++ b/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py @@ -0,0 +1,224 @@ +import json +import re + +from cvss import CVSS3 + +from dojo.models import Finding + + +class JfrogXrayOnDemandBinaryScanParser(object): + """jfrog_xray_scan JSON reports""" + + def get_scan_types(self): + return ["Jfrog Xray On Demand Binary Scan"] + + def get_label_for_scan_types(self, scan_type): + return scan_type + + def get_description_for_scan_types(self, scan_type): + return "Import Xray findings in JSON format." + + def get_findings(self, json_output, test): + tree = json.load(json_output) + return self.get_items(tree, test) + + def get_items(self, tree, test): + items = {} + for data in tree: + if "vulnerabilities" in data: + vulnerability_tree = data["vulnerabilities"] + + for node in vulnerability_tree: + item = get_item(node, test) + + title_cve = "No CVE" + if "cves" in tree: + if "cve" in tree["cves"][0]: + title_cve = tree["cve"] + + unique_key = node.get("issue_id", "") + node.get("summary", "") + title_cve + items[unique_key] = item + + return list(items.values()) + + +def decode_cwe_number(value): + match = re.match(r"CWE-\d+", value, re.IGNORECASE) + if match is None: + return 0 + return int(match[0].rsplit("-")[1]) + + +def get_servery(vulnerability): + if "severity" in vulnerability: + if vulnerability["severity"] == "Unknown": + severity = "Info" + else: + severity = vulnerability["severity"].title() + else: + severity = "Info" + return severity + + +def get_references(vulnerability): + ref = "" + if "references" in vulnerability: + references = vulnerability["references"] + for reference in references: + ref += reference + "\n" + return ref + + +def get_remediation(extended_information): + remediation = "" + if "remediation" in extended_information: + remediation = "\n**Remediation**\n" + remediation += extended_information["remediation"] + "\n" + return remediation + + +def get_severity_justification(vulnerability): + severity_desc = "" + remediation = "" + extended_information = vulnerability.get("extended_information") + if extended_information: + remediation += get_remediation(extended_information) + if "short_description" in extended_information: + severity_desc = "**short description**\n" + severity_desc += extended_information["short_description"] + "\n" + if "full_description" in extended_information: + severity_desc = "**full description**\n" + severity_desc += extended_information["full_description"] + "\n" + if "jfrog_research_severity" in extended_information: + severity_desc = "**jfrog research severity**\n" + severity_desc += extended_information["jfrog_research_severity"] + "\n" + if "jfrog_research_severity_reasons" in extended_information: + severity_desc = "**jfrog research severity reasons**\n" + for item in extended_information["jfrog_research_severity_reasons"]: + severity_desc += item["name"] + "\n" if item.get("name") else "" + severity_desc += item["description"] + "\n" if item.get("description") else "" + return severity_desc, remediation + + +def get_component(vulnerability): + mitigation = "" + gav = "" + impact = "**Impact paths**\n" + if "components" in vulnerability: + components = vulnerability["components"] + gav = next(iter(components)) + component = components[gav] + fixed_versions = component.get("fixed_versions") + if fixed_versions: + mitigation = "**Versions containing a fix:**\n" + mitigation = mitigation + "\n".join(fixed_versions) + if "impact_paths" in component: + impact_paths = component["impact_paths"][0] + for item in impact_paths: + if "component_id" in item: + component_id = item["component_id"] + impact = impact + "\n" + component_id + if "full_path" in item: + full_path = item["full_path"] + impact = impact + "\n" + full_path + return gav, mitigation, impact + + +def get_version_vulnerability(vulnerability): + if "vulnerable_versions" in vulnerability["component_versions"]: + extra_desc = "\n**Versions that are vulnerable:**\n" + extra_desc += "\n".join(vulnerability["component_versions"]["vulnerable_versions"]) + return extra_desc + return "None" + + +def get_provider(vulnerabiity): + if "component_versions" in vulnerabiity: + provider = vulnerabiity.get("component_versions").get("more_details").get("provider") + if provider: + provider += f"\n**Provider:** {provider}" + return provider + return "" + + +def get_etx(vulnerability): + if "EXT" in vulnerability: + return vulnerability["EXT"] + return "" + + +def get_cve(vulnerability): + if "cves" in vulnerability: + cves = vulnerability["cves"] + return cves + return [] + + +def get_item(vulnerability, test): + severity_justification, remediation = get_severity_justification(vulnerability) + severity = get_servery(vulnerability) + references = get_references(vulnerability) + vulnerability_ids = list() + cwe = None + cvssv3 = None + cvss_v3 = "No CVSS v3 score." + extra_desc = "" + # Some entries have no CVE entries, despite they exist. Example CVE-2017-1000502. + cves = get_cve(vulnerability) + if len(cves) > 0: + for item in cves: + if item.get("cve"): + vulnerability_ids.append(item.get("cve")) + # take only the first one for now, limitation of DD model. + if len(cves[0].get("cwe", [])) > 0: + cwe = decode_cwe_number(cves[0].get("cwe", [])[0]) + if "cvss_v3" in cves[0]: + cvss_v3 = cves[0]["cvss_v3"] + # this dedicated package will clean the vector + cvssv3 = CVSS3.from_rh_vector(cvss_v3).clean_vector() + + extra_desc += get_provider(vulnerability) + component_name, mitigation, impact = get_component(vulnerability) + component_version = get_etx(vulnerability) + + # The 'id' field is empty? (at least in my sample file) + if vulnerability_ids: + if vulnerability.get("id"): + title = ( + vulnerability["id"] + + " - " + + str(vulnerability_ids[0]) + + " - " + + component_name + + ":" + + component_version + ) + else: + title = str(vulnerability_ids[0]) + " - " + component_name + ":" + component_version + else: + if vulnerability.get("id"): + title = vulnerability["id"] + " - " + component_name + ":" + component_version + else: + title = "No CVE - " + component_name + ":" + component_version + + # create the finding object + finding = Finding( + title=title, + cwe=cwe, + test=test, + severity_justification=severity_justification, + severity=severity, + description=(vulnerability["summary"] + extra_desc).strip(), + mitigation=mitigation + remediation, + component_name=component_name, + component_version=component_version, + impact=impact, + references=references, + file_path=vulnerability.get("source_comp_id"), + static_finding=True, + dynamic_finding=False, + cvssv3=cvssv3, + ) + if vulnerability_ids: + finding.unsaved_vulnerability_ids = vulnerability_ids + return finding \ No newline at end of file diff --git a/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json b/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json new file mode 100644 index 00000000000..be534784f7f --- /dev/null +++ b/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json @@ -0,0 +1,111 @@ +[ + { + "scan_id": "dd8f-4927-5db6-fb188ae8d984", + "vulnerabilities": [ + { + "cves": [ + { + "cve": "CVE-2017-8923", + "cvss_v2_score": "5.0", + "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:P", + "cvss_v3_score": "7.5", + "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "summary": "Summary of test", + "severity": "High", + "components": { + "gav://org.yaml:snakeyaml:1.16": { + "fixed_versions": [ + "[1.26]" + ], + "impact_paths": [ + [ + { + "component_id": "gav://co.com.test.com" + }, + { + "component_id": "gav://co.com.test.com", + "full_path": "lib/snakeyaml-1.16.jar" + } + ] + ] + } + }, + "issue_id": "XRAY-92904", + "references": [ + "https://test.com.co" + ] + }, + { + "cves": [ + { + "cve": "CVE-2014-0114", + "cvss_v2_score": "7.5", + "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P" + } + ], + "summary": "Summary test", + "severity": "High", + "components": { + "gav://test": { + "fixed_versions": [ + "[1.9.4]" + ], + "impact_paths": [ + [ + { + "component_id": "gav://co.com.test.test:core:1.0.0-test" + }, + { + "component_id": "gav://test", + "full_path": "lib/commons-beanutils-1.9.2.jar" + } + ] + ] + } + }, + "issue_id": "XRAY-55616", + "references": [ + "https://test.com.co" + ] + }, + { + "cves": [ + { + "cvss_v2_score": "7.5", + "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P" + } + ], + "summary": "Summary test", + "severity": "High", + "components": { + "test_item": { + "fixed_versions": [ + "[1.2.8.RELEASE]", + "[1.3.1.RELEASE]" + ], + "impact_paths": [ + [ + { + "component_id": "gav://co.com.test.test:core:1.0.0-test" + }, + { + "component_id": "gav://test.com.co", + "full_path": "lib/test/libtest" + } + ] + ] + } + }, + "issue_id": "XRAY-79870", + "references": [ + "https://test.com.co" + ] + } + ], + "component_id": "gav://co.com.test.test:core:1.0.0-test", + "package_type": "Maven", + "status": "completed" + } + ] diff --git a/unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json b/unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json new file mode 100644 index 00000000000..09bd2903a8f --- /dev/null +++ b/unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json @@ -0,0 +1,43 @@ +[ + { + "scan_id": "dd8f-4927-5db6-fb188ae8d984", + "vulnerabilities": [ + { + "cves": [ + { + "cve": "CVE-2014-0114", + "cvss_v2_score": "7.5", + "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P" + } + ], + "summary": "Summary test", + "severity": "High", + "components": { + "gav://test": { + "fixed_versions": [ + "[1.9.4]" + ], + "impact_paths": [ + [ + { + "component_id": "gav://co.com.test.test:core:1.0.0-test" + }, + { + "component_id": "gav://test", + "full_path": "lib/commons-beanutils-1.9.2.jar" + } + ] + ] + } + }, + "issue_id": "XRAY-55616", + "references": [ + "https://test.com.co" + ] + } + ], + "component_id": "gav://co.com.test.test:core:1.0.0-test", + "package_type": "Maven", + "status": "completed" + } + ] \ No newline at end of file diff --git a/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py b/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py new file mode 100644 index 00000000000..d39f51c2c66 --- /dev/null +++ b/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py @@ -0,0 +1,33 @@ +from ..dojo_test_case import DojoTestCase +from dojo.models import Test, Finding +from dojo.tools.jfrog_xray_on_demand_binary_scan.parser import \ + JfrogXrayOnDemandBinaryScanParser, decode_cwe_number + + +class TestJfrogXrayOnDemandBinaryScanParser(DojoTestCase): + + def test_parse_file_with_one_vuln(self): + testfile = open("unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json") + parser = JfrogXrayOnDemandBinaryScanParser() + findings = parser.get_findings(testfile, Test()) + testfile.close() + self.assertEqual(1, len(findings)) + item: Finding = findings[0] + self.assertEqual("gav://test", item.component_name) + self.assertEqual("CVE-2014-0114", item.unsaved_vulnerability_ids[0]) + self.assertEqual("High", item.severity) + + def test_parse_file_with_many_vulns(self): + testfile = open("unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json") + parser = JfrogXrayOnDemandBinaryScanParser() + findings = parser.get_findings(testfile, Test()) + testfile.close() + self.assertEqual(3, len(findings)) + + def test_decode_cwe_number(self): + with self.subTest(val="CWE-1234"): + self.assertEqual(1234, decode_cwe_number("CWE-1234")) + with self.subTest(val=""): + self.assertEqual(0, decode_cwe_number("")) + with self.subTest(val="cwe-1"): + self.assertEqual(1, decode_cwe_number("cwe-1")) \ No newline at end of file From dc851e4a7af042384900bac5fd4f54a620491e16 Mon Sep 17 00:00:00 2001 From: renejal Date: Wed, 18 Oct 2023 13:55:27 -0500 Subject: [PATCH 2/5] blank line removed --- .../parsers/file/Jfrog_xray_on_demand_binary Scan.md | 2 +- .../scans/jfrog_xray_on_demand_binary_scan/many_vulns.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md b/docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md index d34f88fdf2a..e1e2c6097dd 100644 --- a/docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md +++ b/docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md @@ -6,4 +6,4 @@ Import the JSON format for the \"Jfrog xray on demand binary Scan\" file. Use th -- jfron file documentation -https://jfrog.com/help/r/jfrog-cli/on-demand-binary-scan +https://jfrog.com/help/r/jfrog-cli/on-demand-binary-scan \ No newline at end of file diff --git a/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json b/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json index be534784f7f..3febd121e46 100644 --- a/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json +++ b/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json @@ -108,4 +108,4 @@ "package_type": "Maven", "status": "completed" } - ] + ] \ No newline at end of file From 1d0fe66345353c8f711ddb20b63dee622cff8c3a Mon Sep 17 00:00:00 2001 From: renejal Date: Wed, 18 Oct 2023 13:59:16 -0500 Subject: [PATCH 3/5] line break was added to the end of the file --- dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py | 2 +- .../scans/jfrog_xray_on_demand_binary_scan/many_vulns.json | 3 ++- unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json | 2 +- .../tools/test_jfrog_xray_on_demand_binary_scan_parser.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py b/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py index 8157851d151..1d09e3ea22b 100644 --- a/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py +++ b/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py @@ -221,4 +221,4 @@ def get_item(vulnerability, test): ) if vulnerability_ids: finding.unsaved_vulnerability_ids = vulnerability_ids - return finding \ No newline at end of file + return finding diff --git a/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json b/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json index 3febd121e46..3464d36dfe9 100644 --- a/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json +++ b/unittests/scans/jfrog_xray_on_demand_binary_scan/many_vulns.json @@ -108,4 +108,5 @@ "package_type": "Maven", "status": "completed" } - ] \ No newline at end of file + ] + diff --git a/unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json b/unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json index 09bd2903a8f..0db03c6f2c5 100644 --- a/unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json +++ b/unittests/scans/jfrog_xray_on_demand_binary_scan/one_vuln.json @@ -40,4 +40,4 @@ "package_type": "Maven", "status": "completed" } - ] \ No newline at end of file + ] diff --git a/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py b/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py index d39f51c2c66..4959c2481c8 100644 --- a/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py +++ b/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py @@ -30,4 +30,4 @@ def test_decode_cwe_number(self): with self.subTest(val=""): self.assertEqual(0, decode_cwe_number("")) with self.subTest(val="cwe-1"): - self.assertEqual(1, decode_cwe_number("cwe-1")) \ No newline at end of file + self.assertEqual(1, decode_cwe_number("cwe-1")) From ec4e707a1e47bbbfe8e932db8fee5f03c93b1e3d Mon Sep 17 00:00:00 2001 From: renejal Date: Mon, 13 Nov 2023 21:01:16 -0500 Subject: [PATCH 4/5] rename file.md --- ..._demand_binary Scan.md => jfrog_xray_on_demand_binary_scan.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/content/en/integrations/parsers/file/{Jfrog_xray_on_demand_binary Scan.md => jfrog_xray_on_demand_binary_scan.md} (100%) diff --git a/docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md b/docs/content/en/integrations/parsers/file/jfrog_xray_on_demand_binary_scan.md similarity index 100% rename from docs/content/en/integrations/parsers/file/Jfrog_xray_on_demand_binary Scan.md rename to docs/content/en/integrations/parsers/file/jfrog_xray_on_demand_binary_scan.md From 33ca34afa50aa0e572b3a0c34b3536122dfa5d59 Mon Sep 17 00:00:00 2001 From: renejal Date: Thu, 16 Nov 2023 15:13:40 -0500 Subject: [PATCH 5/5] cleanning pull request --- .dryrunsecurity.yaml | 2 +- docs/content/en/getting_started/upgrading.md | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.dryrunsecurity.yaml b/.dryrunsecurity.yaml index a5f236965c7..a5ccf1ea87f 100644 --- a/.dryrunsecurity.yaml +++ b/.dryrunsecurity.yaml @@ -64,4 +64,4 @@ allowedAuthors: - blakeowens notificationList: - '@mtesauro' - - '@grendel513' + - '@grendel513' \ No newline at end of file diff --git a/docs/content/en/getting_started/upgrading.md b/docs/content/en/getting_started/upgrading.md index 5e566a59987..03ba899c8e0 100644 --- a/docs/content/en/getting_started/upgrading.md +++ b/docs/content/en/getting_started/upgrading.md @@ -72,10 +72,6 @@ godojo installations If you have installed DefectDojo on "iron" and wish to upgrade the installation, please see the [instructions in the repo](https://github.com/DefectDojo/godojo/blob/master/docs-and-scripts/upgrading.md). -## Upgrading to DefectDojo Version 2.28.x. - -There are no special instruction for upgrading to 2.28.0. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.28.0) for the contents of the release. - ## Upgrading to DefectDojo Version 2.27.x. There are no special instruction for upgrading to 2.27.0. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.27.0) for the contents of the release.