From c58f90e26700d174a0fa03b2fafd713685b2fa17 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 10 Nov 2025 17:24:49 +0000 Subject: [PATCH 01/25] Update versions in application files --- components/package.json | 2 +- helm/defectdojo/Chart.yaml | 14 ++++---------- helm/defectdojo/README.md | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/components/package.json b/components/package.json index 6a45f098683..07c351cf814 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.52.1", + "version": "2.53.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 92715d49ce3..3e3ef73d073 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.52.1" +appVersion: "2.53.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.8.1 +version: 1.8.2-dev icon: https://defectdojo.com/hubfs/DefectDojo_favicon.png maintainers: - name: madchap @@ -33,11 +33,5 @@ dependencies: # - kind: security # description: Critical bug annotations: - artifacthub.io/prerelease: "false" - artifacthub.io/changes: | - - kind: fixed - description: Broken rendering of media PVC - - kind: fixed - description: Typo in description of digests - - kind: changed - description: Bump DefectDojo to 2.52.1 + artifacthub.io/prerelease: "true" + artifacthub.io/changes: "" diff --git a/helm/defectdojo/README.md b/helm/defectdojo/README.md index 861a4456c90..aa468e6bc61 100644 --- a/helm/defectdojo/README.md +++ b/helm/defectdojo/README.md @@ -495,7 +495,7 @@ kubectl delete pvc data-defectdojo-redis-0 data-defectdojo-postgresql-0 # General information about chart values -![Version: 1.8.1](https://img.shields.io/badge/Version-1.8.1-informational?style=flat-square) ![AppVersion: 2.52.1](https://img.shields.io/badge/AppVersion-2.52.1-informational?style=flat-square) +![Version: 1.8.2-dev](https://img.shields.io/badge/Version-1.8.2--dev-informational?style=flat-square) ![AppVersion: 2.53.0-dev](https://img.shields.io/badge/AppVersion-2.53.0--dev-informational?style=flat-square) A Helm chart for Kubernetes to install DefectDojo From cf2a8b1614615b1f783e7d4af7fc3c6a98e01100 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:22:15 +0100 Subject: [PATCH 02/25] :bug: harden jfrog xray unified file parsing #13628 (#13632) * :bug: harden jfrog xray unified file parsing * fix --- dojo/tools/jfrog_xray_unified/parser.py | 10 ++++-- .../scans/jfrog_xray_unified/issue_13628.json | 36 +++++++++++++++++++ .../tools/test_jfrog_xray_unified_parser.py | 9 +++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 unittests/scans/jfrog_xray_unified/issue_13628.json diff --git a/dojo/tools/jfrog_xray_unified/parser.py b/dojo/tools/jfrog_xray_unified/parser.py index 83e0222ade0..8aceba5c54d 100644 --- a/dojo/tools/jfrog_xray_unified/parser.py +++ b/dojo/tools/jfrog_xray_unified/parser.py @@ -104,7 +104,8 @@ def get_item(vulnerability, test): else: title = vulnerability["summary"] - references = "\n".join(vulnerability["references"]) + references_str = vulnerability.get("references") + references = "\n".join(references_str) if isinstance(references_str, list) else (references_str if isinstance(references_str, str) else "") scan_time = datetime.strptime( vulnerability["artifact_scan_time"], "%Y-%m-%dT%H:%M:%S%z", @@ -118,7 +119,10 @@ def get_item(vulnerability, test): # remove package type from component name component_name = component_name.split("://", 1)[1] - tags = ["packagetype_" + vulnerability["package_type"]] + tags = [] + package_type = vulnerability.get("package_type") + if package_type: + tags.append("packagetype_" + package_type) # create the finding object finding = Finding( @@ -126,7 +130,7 @@ def get_item(vulnerability, test): test=test, severity=severity, description=( - vulnerability["description"] + "\n\n" + extra_desc + vulnerability.get("description", vulnerability.get("summary")) + "\n\n" + extra_desc ).strip(), mitigation=mitigation, component_name=component_name, diff --git a/unittests/scans/jfrog_xray_unified/issue_13628.json b/unittests/scans/jfrog_xray_unified/issue_13628.json new file mode 100644 index 00000000000..6cf5a92926a --- /dev/null +++ b/unittests/scans/jfrog_xray_unified/issue_13628.json @@ -0,0 +1,36 @@ +{ + "total_rows": 123, + "rows": [ + { + "cves": [ + { + "cve": "CVE-2023-42282", + "cvss_v3_score": 9.8, + "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + } + ], + "cvss3_max_score": 9.8, + "severity": "Critical", + "component_physical_path": "ip:2.0.0", + "impact_path": [ + "somepath" + ], + "fixed_versions": [ + "2.0.1", + "1.1.9" + ], + "issue_id": "XRAY-123", + "project_keys": [ + "somepath" + ], + "applicability": null, + "applicability_result": "not_scanned", + "summary": "The ip package before 1.1.9 for Node.js might allow SSRF because some IP addresses (such as 0x7f.1) are improperly categorized as globally routable via isPublic.", + "vulnerable_component": "npm://ip:2.0.0", + "impacted_artifact": "build://[some_artifact_id]", + "path": "somepath", + "published": "2024-02-09T16:30:10Z", + "artifact_scan_time": "2025-11-03T11:42:09Z" + } + ] +} \ No newline at end of file diff --git a/unittests/tools/test_jfrog_xray_unified_parser.py b/unittests/tools/test_jfrog_xray_unified_parser.py index 52b673308c4..92bc30c75ff 100644 --- a/unittests/tools/test_jfrog_xray_unified_parser.py +++ b/unittests/tools/test_jfrog_xray_unified_parser.py @@ -345,3 +345,12 @@ def test_parse_file_with_another_report(self): findings = parser.get_findings(testfile, Test()) testfile.close() self.assertEqual(7, len(findings)) + + def test_parse_file_issue_13628(self): + testfile = (get_unit_tests_scans_path("jfrog_xray_unified") / "issue_13628.json").open(encoding="utf-8") + parser = JFrogXrayUnifiedParser() + findings = parser.get_findings(testfile, Test()) + testfile.close() + self.assertEqual(1, len(findings)) + self.assertEqual("Critical", findings[0].severity) + self.assertEqual("XRAY-123 - The ip package before 1.1.9 for Node.js might allow SSRF because some IP addresses (such as 0x7f.1) are improperly categorized as globally routable via isPublic.", findings[0].title) From c484229d16f4eb02b89621fca06109432e3b34fa Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:01:47 +0000 Subject: [PATCH 03/25] feat(renovate): track oldest maintained k8s (#13670) Signed-off-by: kiblik <5609770+kiblik@users.noreply.github.com> --- .github/renovate.json | 9 +++++++++ .github/workflows/k8s-tests.yml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index a85506ce23c..a5ae6324179 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -28,6 +28,15 @@ "matchPackageNames": "renovatebot/renovate", "schedule": ["* * * * 0"] }], + "customDatasources": { + "endoflife-oldest-maintained": { + "defaultRegistryUrlTemplate": "https://endoflife.date/api/v1/products/{{packageName}}", + "format": "json", + "transformTemplates": [ + "{ \"releases\": [$.result.releases[isMaintained = true]^( Date: Wed, 12 Nov 2025 17:30:25 +0100 Subject: [PATCH 04/25] :lipstick: beautify drheader jsonfiles (#13672) * :lipstick: beautify drheader jsonfiles * add more json --- unittests/scans/drheader/scan2.json | 73 +++++++++++- unittests/scans/drheader/scan3.json | 105 +++++++++++++++++- .../scans/intsights/intsights_zero_vuln.json | 4 +- .../mend-sca-platform-api3-no-findings.json | 9 +- unittests/scans/mend/okhttp_no_vuln.json | 6 +- unittests/scans/semgrep/empty.json | 5 +- unittests/scans/semgrep_pro/no_vuln.json | 3 +- unittests/scans/snyk_issue_api/empty.json | 8 +- .../scans/whitehat_sentinel/empty_file.json | 4 +- 9 files changed, 201 insertions(+), 16 deletions(-) diff --git a/unittests/scans/drheader/scan2.json b/unittests/scans/drheader/scan2.json index 061323da810..5ab2670d5dd 100644 --- a/unittests/scans/drheader/scan2.json +++ b/unittests/scans/drheader/scan2.json @@ -1 +1,72 @@ -[{"rule": "Content-Security-Policy", "severity": "high", "message": "Must-Contain-One directive missed", "expected": ["default-src 'none'", "default-src 'self'"], "delimiter": ";", "value": "default-src 'self' service.maxymiser.net; child-src 'self' 'unsafe-inline' https://www.googleadservices.com https://*.fls.doubleclick.net/ https://*.santander.co.uk https://santander.demdex.net; script-src 'self' 'unsafe-inline' https://track.omguk.com https://cdn.usersnap.com https://screencapture.kampyle.com https://nebula-cdn.kampyle.com https://resources.digital-cloud-uk.medallia.eu https://pagead2.googlesyndication.com https://js-cdn.dynatrace.com https://activitymap.adobe.com https://cdn-ukwest.onetrust.com https://cdn.mouseflow.com https://googleads.g.doubleclick.net lptag.liveperson.net lo.v.liveperson.net lo.msg.liveperson.net accdn.lpsnmedia.net lpcdn.lpsnmedia.net https://www.googletagservices.com https://ad.doubleclick.net service.maxymiser.net https://connect.facebook.net https://*.fls.doubleclick.net/ https://www.googleadservices.com https://www.googletagmanager.com https://assets.adobedtm.com https://dpm.demdex.net/ https://*.santander.co.uk 'unsafe-eval'; connect-src 'self' 'unsafe-inline' https://udc-neb.kampyle.com https://*.bf.dynatrace.com https://privacyportal-uk.onetrust.com https://cdn-ukwest.onetrust.com https://o2.mouseflow.com https://googleads4.g.doubleclick.net wss://lo.msg.liveperson.net https://dpm.demdex.net https://*.santander.co.uk; img-src 'self' https://lpcdn.lpsnmedia.net service.maxymiser.net 'unsafe-inline' https://*.santander.co.uk data: https:; style-src 'self' service.maxymiser.net 'unsafe-inline'; font-src 'self'; frame-src 'self' 'unsafe-inline' https://www.youtube-nocookie.com https://resources.digital-cloud-uk.medallia.eu https://lo.tokenizer.liveperson.net https://lo.msghist.liveperson.net https://lo.msg.liveperson.net https://lpcdn.lpsnmedia.net lo.idp.liveperson.net server.lon.liveperson.net https://authorize.omniture.com https://sitecatalyst.omniture.com service.maxymiser.net https://edigitalsurvey.com https://www.youtube.com https://santander.demdex.net https://*.fls.doubleclick.net; object-src 'self'; media-src lpcdn.lpsnmedia.net; worker-src blob:;", "anomaly": ["default-src 'none'", "default-src 'self'"]}, {"rule": "Content-Security-Policy", "severity": "medium", "message": "Must-Avoid directive included", "avoid": ["unsafe-inline", "unsafe-eval"], "delimiter": ";", "value": "default-src 'self' service.maxymiser.net; child-src 'self' 'unsafe-inline' https://www.googleadservices.com https://*.fls.doubleclick.net/ https://*.santander.co.uk https://santander.demdex.net; script-src 'self' 'unsafe-inline' https://track.omguk.com https://cdn.usersnap.com https://screencapture.kampyle.com https://nebula-cdn.kampyle.com https://resources.digital-cloud-uk.medallia.eu https://pagead2.googlesyndication.com https://js-cdn.dynatrace.com https://activitymap.adobe.com https://cdn-ukwest.onetrust.com https://cdn.mouseflow.com https://googleads.g.doubleclick.net lptag.liveperson.net lo.v.liveperson.net lo.msg.liveperson.net accdn.lpsnmedia.net lpcdn.lpsnmedia.net https://www.googletagservices.com https://ad.doubleclick.net service.maxymiser.net https://connect.facebook.net https://*.fls.doubleclick.net/ https://www.googleadservices.com https://www.googletagmanager.com https://assets.adobedtm.com https://dpm.demdex.net/ https://*.santander.co.uk 'unsafe-eval'; connect-src 'self' 'unsafe-inline' https://udc-neb.kampyle.com https://*.bf.dynatrace.com https://privacyportal-uk.onetrust.com https://cdn-ukwest.onetrust.com https://o2.mouseflow.com https://googleads4.g.doubleclick.net wss://lo.msg.liveperson.net https://dpm.demdex.net https://*.santander.co.uk; img-src 'self' https://lpcdn.lpsnmedia.net service.maxymiser.net 'unsafe-inline' https://*.santander.co.uk data: https:; style-src 'self' service.maxymiser.net 'unsafe-inline'; font-src 'self'; frame-src 'self' 'unsafe-inline' https://www.youtube-nocookie.com https://resources.digital-cloud-uk.medallia.eu https://lo.tokenizer.liveperson.net https://lo.msghist.liveperson.net https://lo.msg.liveperson.net https://lpcdn.lpsnmedia.net lo.idp.liveperson.net server.lon.liveperson.net https://authorize.omniture.com https://sitecatalyst.omniture.com service.maxymiser.net https://edigitalsurvey.com https://www.youtube.com https://santander.demdex.net https://*.fls.doubleclick.net; object-src 'self'; media-src lpcdn.lpsnmedia.net; worker-src blob:;", "anomaly": "unsafe-inline"}, {"rule": "Content-Security-Policy", "severity": "medium", "message": "Must-Avoid directive included", "avoid": ["unsafe-inline", "unsafe-eval"], "delimiter": ";", "value": "default-src 'self' service.maxymiser.net; child-src 'self' 'unsafe-inline' https://www.googleadservices.com https://*.fls.doubleclick.net/ https://*.santander.co.uk https://santander.demdex.net; script-src 'self' 'unsafe-inline' https://track.omguk.com https://cdn.usersnap.com https://screencapture.kampyle.com https://nebula-cdn.kampyle.com https://resources.digital-cloud-uk.medallia.eu https://pagead2.googlesyndication.com https://js-cdn.dynatrace.com https://activitymap.adobe.com https://cdn-ukwest.onetrust.com https://cdn.mouseflow.com https://googleads.g.doubleclick.net lptag.liveperson.net lo.v.liveperson.net lo.msg.liveperson.net accdn.lpsnmedia.net lpcdn.lpsnmedia.net https://www.googletagservices.com https://ad.doubleclick.net service.maxymiser.net https://connect.facebook.net https://*.fls.doubleclick.net/ https://www.googleadservices.com https://www.googletagmanager.com https://assets.adobedtm.com https://dpm.demdex.net/ https://*.santander.co.uk 'unsafe-eval'; connect-src 'self' 'unsafe-inline' https://udc-neb.kampyle.com https://*.bf.dynatrace.com https://privacyportal-uk.onetrust.com https://cdn-ukwest.onetrust.com https://o2.mouseflow.com https://googleads4.g.doubleclick.net wss://lo.msg.liveperson.net https://dpm.demdex.net https://*.santander.co.uk; img-src 'self' https://lpcdn.lpsnmedia.net service.maxymiser.net 'unsafe-inline' https://*.santander.co.uk data: https:; style-src 'self' service.maxymiser.net 'unsafe-inline'; font-src 'self'; frame-src 'self' 'unsafe-inline' https://www.youtube-nocookie.com https://resources.digital-cloud-uk.medallia.eu https://lo.tokenizer.liveperson.net https://lo.msghist.liveperson.net https://lo.msg.liveperson.net https://lpcdn.lpsnmedia.net lo.idp.liveperson.net server.lon.liveperson.net https://authorize.omniture.com https://sitecatalyst.omniture.com service.maxymiser.net https://edigitalsurvey.com https://www.youtube.com https://santander.demdex.net https://*.fls.doubleclick.net; object-src 'self'; media-src lpcdn.lpsnmedia.net; worker-src blob:;", "anomaly": "unsafe-eval"}, {"rule": "Strict-Transport-Security", "severity": "high", "message": "Header not included in response", "expected": ["max-age=31536000", "includesubdomains"], "delimiter": ";"}, {"rule": "Cache-Control", "severity": "high", "message": "Value does not match security policy", "expected": ["no-cache", "no-store", "must-revalidate"], "delimiter": ",", "value": "private, must-revalidate, max-age=900"}, {"rule": "Pragma", "severity": "high", "message": "Header not included in response", "expected": ["no-cache"], "delimiter": ";"}] +[ + { + "rule": "Content-Security-Policy", + "severity": "high", + "message": "Must-Contain-One directive missed", + "expected": [ + "default-src 'none'", + "default-src 'self'" + ], + "delimiter": ";", + "value": "default-src 'self' service.maxymiser.net; child-src 'self' 'unsafe-inline' https://www.googleadservices.com https://*.fls.doubleclick.net/ https://*.santander.co.uk https://santander.demdex.net; script-src 'self' 'unsafe-inline' https://track.omguk.com https://cdn.usersnap.com https://screencapture.kampyle.com https://nebula-cdn.kampyle.com https://resources.digital-cloud-uk.medallia.eu https://pagead2.googlesyndication.com https://js-cdn.dynatrace.com https://activitymap.adobe.com https://cdn-ukwest.onetrust.com https://cdn.mouseflow.com https://googleads.g.doubleclick.net lptag.liveperson.net lo.v.liveperson.net lo.msg.liveperson.net accdn.lpsnmedia.net lpcdn.lpsnmedia.net https://www.googletagservices.com https://ad.doubleclick.net service.maxymiser.net https://connect.facebook.net https://*.fls.doubleclick.net/ https://www.googleadservices.com https://www.googletagmanager.com https://assets.adobedtm.com https://dpm.demdex.net/ https://*.santander.co.uk 'unsafe-eval'; connect-src 'self' 'unsafe-inline' https://udc-neb.kampyle.com https://*.bf.dynatrace.com https://privacyportal-uk.onetrust.com https://cdn-ukwest.onetrust.com https://o2.mouseflow.com https://googleads4.g.doubleclick.net wss://lo.msg.liveperson.net https://dpm.demdex.net https://*.santander.co.uk; img-src 'self' https://lpcdn.lpsnmedia.net service.maxymiser.net 'unsafe-inline' https://*.santander.co.uk data: https:; style-src 'self' service.maxymiser.net 'unsafe-inline'; font-src 'self'; frame-src 'self' 'unsafe-inline' https://www.youtube-nocookie.com https://resources.digital-cloud-uk.medallia.eu https://lo.tokenizer.liveperson.net https://lo.msghist.liveperson.net https://lo.msg.liveperson.net https://lpcdn.lpsnmedia.net lo.idp.liveperson.net server.lon.liveperson.net https://authorize.omniture.com https://sitecatalyst.omniture.com service.maxymiser.net https://edigitalsurvey.com https://www.youtube.com https://santander.demdex.net https://*.fls.doubleclick.net; object-src 'self'; media-src lpcdn.lpsnmedia.net; worker-src blob:;", + "anomaly": [ + "default-src 'none'", + "default-src 'self'" + ] + }, + { + "rule": "Content-Security-Policy", + "severity": "medium", + "message": "Must-Avoid directive included", + "avoid": [ + "unsafe-inline", + "unsafe-eval" + ], + "delimiter": ";", + "value": "default-src 'self' service.maxymiser.net; child-src 'self' 'unsafe-inline' https://www.googleadservices.com https://*.fls.doubleclick.net/ https://*.santander.co.uk https://santander.demdex.net; script-src 'self' 'unsafe-inline' https://track.omguk.com https://cdn.usersnap.com https://screencapture.kampyle.com https://nebula-cdn.kampyle.com https://resources.digital-cloud-uk.medallia.eu https://pagead2.googlesyndication.com https://js-cdn.dynatrace.com https://activitymap.adobe.com https://cdn-ukwest.onetrust.com https://cdn.mouseflow.com https://googleads.g.doubleclick.net lptag.liveperson.net lo.v.liveperson.net lo.msg.liveperson.net accdn.lpsnmedia.net lpcdn.lpsnmedia.net https://www.googletagservices.com https://ad.doubleclick.net service.maxymiser.net https://connect.facebook.net https://*.fls.doubleclick.net/ https://www.googleadservices.com https://www.googletagmanager.com https://assets.adobedtm.com https://dpm.demdex.net/ https://*.santander.co.uk 'unsafe-eval'; connect-src 'self' 'unsafe-inline' https://udc-neb.kampyle.com https://*.bf.dynatrace.com https://privacyportal-uk.onetrust.com https://cdn-ukwest.onetrust.com https://o2.mouseflow.com https://googleads4.g.doubleclick.net wss://lo.msg.liveperson.net https://dpm.demdex.net https://*.santander.co.uk; img-src 'self' https://lpcdn.lpsnmedia.net service.maxymiser.net 'unsafe-inline' https://*.santander.co.uk data: https:; style-src 'self' service.maxymiser.net 'unsafe-inline'; font-src 'self'; frame-src 'self' 'unsafe-inline' https://www.youtube-nocookie.com https://resources.digital-cloud-uk.medallia.eu https://lo.tokenizer.liveperson.net https://lo.msghist.liveperson.net https://lo.msg.liveperson.net https://lpcdn.lpsnmedia.net lo.idp.liveperson.net server.lon.liveperson.net https://authorize.omniture.com https://sitecatalyst.omniture.com service.maxymiser.net https://edigitalsurvey.com https://www.youtube.com https://santander.demdex.net https://*.fls.doubleclick.net; object-src 'self'; media-src lpcdn.lpsnmedia.net; worker-src blob:;", + "anomaly": "unsafe-inline" + }, + { + "rule": "Content-Security-Policy", + "severity": "medium", + "message": "Must-Avoid directive included", + "avoid": [ + "unsafe-inline", + "unsafe-eval" + ], + "delimiter": ";", + "value": "default-src 'self' service.maxymiser.net; child-src 'self' 'unsafe-inline' https://www.googleadservices.com https://*.fls.doubleclick.net/ https://*.santander.co.uk https://santander.demdex.net; script-src 'self' 'unsafe-inline' https://track.omguk.com https://cdn.usersnap.com https://screencapture.kampyle.com https://nebula-cdn.kampyle.com https://resources.digital-cloud-uk.medallia.eu https://pagead2.googlesyndication.com https://js-cdn.dynatrace.com https://activitymap.adobe.com https://cdn-ukwest.onetrust.com https://cdn.mouseflow.com https://googleads.g.doubleclick.net lptag.liveperson.net lo.v.liveperson.net lo.msg.liveperson.net accdn.lpsnmedia.net lpcdn.lpsnmedia.net https://www.googletagservices.com https://ad.doubleclick.net service.maxymiser.net https://connect.facebook.net https://*.fls.doubleclick.net/ https://www.googleadservices.com https://www.googletagmanager.com https://assets.adobedtm.com https://dpm.demdex.net/ https://*.santander.co.uk 'unsafe-eval'; connect-src 'self' 'unsafe-inline' https://udc-neb.kampyle.com https://*.bf.dynatrace.com https://privacyportal-uk.onetrust.com https://cdn-ukwest.onetrust.com https://o2.mouseflow.com https://googleads4.g.doubleclick.net wss://lo.msg.liveperson.net https://dpm.demdex.net https://*.santander.co.uk; img-src 'self' https://lpcdn.lpsnmedia.net service.maxymiser.net 'unsafe-inline' https://*.santander.co.uk data: https:; style-src 'self' service.maxymiser.net 'unsafe-inline'; font-src 'self'; frame-src 'self' 'unsafe-inline' https://www.youtube-nocookie.com https://resources.digital-cloud-uk.medallia.eu https://lo.tokenizer.liveperson.net https://lo.msghist.liveperson.net https://lo.msg.liveperson.net https://lpcdn.lpsnmedia.net lo.idp.liveperson.net server.lon.liveperson.net https://authorize.omniture.com https://sitecatalyst.omniture.com service.maxymiser.net https://edigitalsurvey.com https://www.youtube.com https://santander.demdex.net https://*.fls.doubleclick.net; object-src 'self'; media-src lpcdn.lpsnmedia.net; worker-src blob:;", + "anomaly": "unsafe-eval" + }, + { + "rule": "Strict-Transport-Security", + "severity": "high", + "message": "Header not included in response", + "expected": [ + "max-age=31536000", + "includesubdomains" + ], + "delimiter": ";" + }, + { + "rule": "Cache-Control", + "severity": "high", + "message": "Value does not match security policy", + "expected": [ + "no-cache", + "no-store", + "must-revalidate" + ], + "delimiter": ",", + "value": "private, must-revalidate, max-age=900" + }, + { + "rule": "Pragma", + "severity": "high", + "message": "Header not included in response", + "expected": [ + "no-cache" + ], + "delimiter": ";" + } +] \ No newline at end of file diff --git a/unittests/scans/drheader/scan3.json b/unittests/scans/drheader/scan3.json index 7867926fd86..f8d5838f055 100644 --- a/unittests/scans/drheader/scan3.json +++ b/unittests/scans/drheader/scan3.json @@ -1 +1,104 @@ -[{"rule": "Content-Security-Policy", "severity": "high", "message": "Header not included in response"}, {"rule": "X-XSS-Protection", "severity": "high", "message": "Value does not match security policy", "expected": ["1", "mode=block"], "delimiter": ";", "value": "0"}, {"rule": "Server", "severity": "high", "message": "Header should not be returned"}, {"rule": "Strict-Transport-Security", "severity": "high", "message": "Header not included in response", "expected": ["max-age=31536000", "includesubdomains"], "delimiter": ";"}, {"rule": "X-Content-Type-Options", "severity": "high", "message": "Header not included in response", "expected": ["nosniff"], "delimiter": ";"}, {"rule": "Set-Cookie", "severity": "high", "message": "Must-Contain directive missed", "expected": ["httponly", "secure"], "delimiter": ";", "value": "nid=208=d8xko0gp8g_pycvdqrwtvdpdiu_7es-hyvqugfqshzyjz5sozpy3y0ayn4kzdkpuzz-ylqjsydscnyuf58liz54ytg7by8smauul5noxicgela-oyi5lu4d_juan8geufgyxg1xao2bqronqyiplvbivs-nndfbywyjwnz0myso; expires=wed, 11-aug-2021 16:59:02 gmt; path=/; domain=.google.com; httponly", "anomaly": "secure"}, {"rule": "Set-Cookie", "severity": "medium", "message": "Must-Contain directive missed", "expected": ["httponly", "secure"], "delimiter": ";", "value": "consent=pending+061; expires=fri, 01-jan-2038 00:00:00 gmt; path=/; domain=.google.com", "anomaly": "httponly"}, {"rule": "Set-Cookie", "severity": "high", "message": "Must-Contain directive missed", "expected": ["httponly", "secure"], "delimiter": ";", "value": "consent=pending+061; expires=fri, 01-jan-2038 00:00:00 gmt; path=/; domain=.google.com", "anomaly": "secure"}, {"rule": "Referrer-Policy", "severity": "high", "message": "Header not included in response"}, {"rule": "Cache-Control", "severity": "high", "message": "Value does not match security policy", "expected": ["no-cache", "no-store", "must-revalidate"], "delimiter": ",", "value": "private, max-age=0"}, {"rule": "Pragma", "severity": "high", "message": "Header not included in response", "expected": ["no-cache"], "delimiter": ";"}] +[ + { + "rule": "Content-Security-Policy", + "severity": "high", + "message": "Header not included in response" + }, + { + "rule": "X-XSS-Protection", + "severity": "high", + "message": "Value does not match security policy", + "expected": [ + "1", + "mode=block" + ], + "delimiter": ";", + "value": "0" + }, + { + "rule": "Server", + "severity": "high", + "message": "Header should not be returned" + }, + { + "rule": "Strict-Transport-Security", + "severity": "high", + "message": "Header not included in response", + "expected": [ + "max-age=31536000", + "includesubdomains" + ], + "delimiter": ";" + }, + { + "rule": "X-Content-Type-Options", + "severity": "high", + "message": "Header not included in response", + "expected": [ + "nosniff" + ], + "delimiter": ";" + }, + { + "rule": "Set-Cookie", + "severity": "high", + "message": "Must-Contain directive missed", + "expected": [ + "httponly", + "secure" + ], + "delimiter": ";", + "value": "nid=208=d8xko0gp8g_pycvdqrwtvdpdiu_7es-hyvqugfqshzyjz5sozpy3y0ayn4kzdkpuzz-ylqjsydscnyuf58liz54ytg7by8smauul5noxicgela-oyi5lu4d_juan8geufgyxg1xao2bqronqyiplvbivs-nndfbywyjwnz0myso; expires=wed, 11-aug-2021 16:59:02 gmt; path=/; domain=.google.com; httponly", + "anomaly": "secure" + }, + { + "rule": "Set-Cookie", + "severity": "medium", + "message": "Must-Contain directive missed", + "expected": [ + "httponly", + "secure" + ], + "delimiter": ";", + "value": "consent=pending+061; expires=fri, 01-jan-2038 00:00:00 gmt; path=/; domain=.google.com", + "anomaly": "httponly" + }, + { + "rule": "Set-Cookie", + "severity": "high", + "message": "Must-Contain directive missed", + "expected": [ + "httponly", + "secure" + ], + "delimiter": ";", + "value": "consent=pending+061; expires=fri, 01-jan-2038 00:00:00 gmt; path=/; domain=.google.com", + "anomaly": "secure" + }, + { + "rule": "Referrer-Policy", + "severity": "high", + "message": "Header not included in response" + }, + { + "rule": "Cache-Control", + "severity": "high", + "message": "Value does not match security policy", + "expected": [ + "no-cache", + "no-store", + "must-revalidate" + ], + "delimiter": ",", + "value": "private, max-age=0" + }, + { + "rule": "Pragma", + "severity": "high", + "message": "Header not included in response", + "expected": [ + "no-cache" + ], + "delimiter": ";" + } +] \ No newline at end of file diff --git a/unittests/scans/intsights/intsights_zero_vuln.json b/unittests/scans/intsights/intsights_zero_vuln.json index da0b981bbae..34fe8994ead 100644 --- a/unittests/scans/intsights/intsights_zero_vuln.json +++ b/unittests/scans/intsights/intsights_zero_vuln.json @@ -1 +1,3 @@ -{"Alerts": []} \ No newline at end of file +{ + "Alerts": [] +} \ No newline at end of file diff --git a/unittests/scans/mend/mend-sca-platform-api3-no-findings.json b/unittests/scans/mend/mend-sca-platform-api3-no-findings.json index 9df1c1c1a27..96e072f0e2e 100644 --- a/unittests/scans/mend/mend-sca-platform-api3-no-findings.json +++ b/unittests/scans/mend/mend-sca-platform-api3-no-findings.json @@ -1 +1,8 @@ -{"additionalData": {"totalItems": 0, "paging": {}}, "supportToken": "123442284e284dddb0652ff65c9f3ebd1731540952924", "response": []} \ No newline at end of file +{ + "additionalData": { + "totalItems": 0, + "paging": {} + }, + "supportToken": "123442284e284dddb0652ff65c9f3ebd1731540952924", + "response": [] +} \ No newline at end of file diff --git a/unittests/scans/mend/okhttp_no_vuln.json b/unittests/scans/mend/okhttp_no_vuln.json index 831bf814c0b..88dc06c72d7 100644 --- a/unittests/scans/mend/okhttp_no_vuln.json +++ b/unittests/scans/mend/okhttp_no_vuln.json @@ -1,5 +1,3 @@ -{ - "vulnerabilities":[ - - ] +{ + "vulnerabilities": [] } \ No newline at end of file diff --git a/unittests/scans/semgrep/empty.json b/unittests/scans/semgrep/empty.json index 3cbbe070d1b..bed8fbdc619 100644 --- a/unittests/scans/semgrep/empty.json +++ b/unittests/scans/semgrep/empty.json @@ -1 +1,4 @@ -{"results": [], "errors": []} \ No newline at end of file +{ + "results": [], + "errors": [] +} \ No newline at end of file diff --git a/unittests/scans/semgrep_pro/no_vuln.json b/unittests/scans/semgrep_pro/no_vuln.json index 5865a185a47..ef24ab5c42c 100644 --- a/unittests/scans/semgrep_pro/no_vuln.json +++ b/unittests/scans/semgrep_pro/no_vuln.json @@ -1,4 +1,3 @@ { - "findings": [ - ] + "findings": [] } \ No newline at end of file diff --git a/unittests/scans/snyk_issue_api/empty.json b/unittests/scans/snyk_issue_api/empty.json index e6b11fc0300..28dda1029e0 100644 --- a/unittests/scans/snyk_issue_api/empty.json +++ b/unittests/scans/snyk_issue_api/empty.json @@ -1,6 +1,6 @@ { - "jsonapi": { - "version": "1.0" - }, - "data": [] + "jsonapi": { + "version": "1.0" + }, + "data": [] } \ No newline at end of file diff --git a/unittests/scans/whitehat_sentinel/empty_file.json b/unittests/scans/whitehat_sentinel/empty_file.json index 8d64b7c1015..0467e59e0c8 100644 --- a/unittests/scans/whitehat_sentinel/empty_file.json +++ b/unittests/scans/whitehat_sentinel/empty_file.json @@ -1 +1,3 @@ -{"collection": []} \ No newline at end of file +{ + "collection": [] +} \ No newline at end of file From c7432c1112be9ccd3162a7fc5aa8310e163990bb Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:30:46 +0100 Subject: [PATCH 05/25] :tada: Advance ibm app parser with fix_available (#13663) * :tada: Advance ibm app parser with fix_available * fix --- dojo/tools/ibm_app/parser.py | 4 ++++ unittests/tools/test_ibm_app_parser.py | 1 + 2 files changed, 5 insertions(+) diff --git a/dojo/tools/ibm_app/parser.py b/dojo/tools/ibm_app/parser.py index 224395e1edc..762be2139dd 100644 --- a/dojo/tools/ibm_app/parser.py +++ b/dojo/tools/ibm_app/parser.py @@ -106,6 +106,10 @@ def get_findings(self, file, test): finding.unsaved_vulnerability_ids = [ vulnerability_id, ] + if recommendation_data: + finding.fix_available = True + else: + finding.fix_available = False finding.unsaved_endpoints = [] dupes[dupe_key] = finding diff --git a/unittests/tools/test_ibm_app_parser.py b/unittests/tools/test_ibm_app_parser.py index 1a66f86d656..6eaacee2cca 100644 --- a/unittests/tools/test_ibm_app_parser.py +++ b/unittests/tools/test_ibm_app_parser.py @@ -23,3 +23,4 @@ def test_parse_file(self): finding = findings[1] self.assertEqual("Info", finding.severity) + self.assertEqual(True, finding.fix_available) From 3396e7b2a55cde76236c80f3d4675c6b2be565e5 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:31:01 +0100 Subject: [PATCH 06/25] :tada: add Dawnscanner fix_available field. (#13660) * :tada: Advance Dawnscanner with fix_available * update --- dojo/tools/dawnscanner/parser.py | 5 ++- .../scans/dawnscanner/dawnscanner_v1.6.9.json | 2 +- unittests/tools/test_dawnscanner_parser.py | 32 ++++++------------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/dojo/tools/dawnscanner/parser.py b/dojo/tools/dawnscanner/parser.py index c2b9ab930a2..375ff073798 100644 --- a/dojo/tools/dawnscanner/parser.py +++ b/dojo/tools/dawnscanner/parser.py @@ -30,7 +30,6 @@ def get_findings(self, filename, test): if item["message"][0:2] != "b," else item["message"][0:-1] ) - finding = Finding( title=item["name"], test=test, @@ -42,6 +41,10 @@ def get_findings(self, filename, test): static_finding=True, dynamic_finding=False, ) + if item.get("remediation"): + finding.fix_available = True + else: + finding.fix_available = False if self.CVE_REGEX.match(item["name"]): finding.unsaved_vulnerability_ids = [ diff --git a/unittests/scans/dawnscanner/dawnscanner_v1.6.9.json b/unittests/scans/dawnscanner/dawnscanner_v1.6.9.json index 7f9afcb7261..46b9075387a 100644 --- a/unittests/scans/dawnscanner/dawnscanner_v1.6.9.json +++ b/unittests/scans/dawnscanner/dawnscanner_v1.6.9.json @@ -31,7 +31,7 @@ "severity": "info", "cvss_score": " ", "message": "Ruby on Rails has specific, built in support for CSRF tokens. To enable it, or ensure that it is enabled, find the base ApplicationController and look for the protect_from_forgery directive. Note that by default Rails does not provide CSRF protection for any HTTP GET request.", - "remediation": "Make sure you are using Rails protect_from_forgery facilities in application_controller.rMake sure you are using Rails protect_from_forgery facilities in application_controller.rb" + "remediation": "" }, { "name": "Owasp Ror CheatSheet: Security Related Headers", "cve_link": "http://cve.mitre.org/cgi-bin/cvename.cgi?name=Owasp Ror CheatSheet: Security Related Headers", diff --git a/unittests/tools/test_dawnscanner_parser.py b/unittests/tools/test_dawnscanner_parser.py index dd42b37ad0a..6d7107c83b5 100644 --- a/unittests/tools/test_dawnscanner_parser.py +++ b/unittests/tools/test_dawnscanner_parser.py @@ -13,38 +13,24 @@ def test_burp_with_one_vuln_has_one_finding(self): for finding in findings: for endpoint in finding.unsaved_endpoints: endpoint.clean() - self.assertEqual(4, len(findings)) - with self.subTest(i=0): finding = findings[0] self.assertEqual("CVE-2016-6316", finding.title) self.assertEqual("Medium", finding.severity) self.assertEqual(1, len(finding.unsaved_vulnerability_ids)) self.assertEqual("CVE-2016-6316", finding.unsaved_vulnerability_ids[0]) - self.assertEqual( - 'Text declared as "HTML safe" when passed as an attribute value to a tag helper will not have quotes escaped which can lead to an XSS attack.', - finding.description, - ) - self.assertEqual( - datetime.datetime(2019, 4, 1, 21, 14, 32, tzinfo=datetime.timezone(datetime.timedelta(seconds=0))), - finding.date, - ) # 2019-04-01 21:14:32 +0000 - + self.assertEqual(finding.description, 'Text declared as "HTML safe" when passed as an attribute value to a tag helper will not have quotes escaped which can lead to an XSS attack.') + self.assertEqual(datetime.datetime(2019, 4, 1, 21, 14, 32, tzinfo=datetime.timezone(datetime.timedelta(seconds=0))), finding.date) # 2019-04-01 21:14:32 +0000 + with self.subTest(i=2): + finding = findings[2] + self.assertEqual(False, finding.fix_available) with self.subTest(i=3): finding = findings[3] self.assertEqual("Owasp Ror CheatSheet: Security Related Headers", finding.title) self.assertEqual("Info", finding.severity) self.assertIsNone(finding.unsaved_vulnerability_ids) - self.assertEqual( - 'To set a header value, simply access the response.headers object as a hash inside your controller (often in a before/after_filter). Rails 4 provides the "default_headers" functionality that will automatically apply the values supplied. This works for most headers in almost all cases.', - finding.description, - ) - self.assertEqual( - "Use response headers like X-Frame-Options, X-Content-Type-Options, X-XSS-Protection in your project.", - finding.mitigation, - ) - self.assertEqual( - datetime.datetime(2019, 4, 1, 21, 14, 32, tzinfo=datetime.timezone(datetime.timedelta(seconds=0))), - finding.date, - ) # 2019-04-01 21:14:32 +0000 + self.assertEqual(finding.description, 'To set a header value, simply access the response.headers object as a hash inside your controller (often in a before/after_filter). Rails 4 provides the "default_headers" functionality that will automatically apply the values supplied. This works for most headers in almost all cases.') + self.assertEqual("Use response headers like X-Frame-Options, X-Content-Type-Options, X-XSS-Protection in your project.", finding.mitigation) + self.assertEqual(datetime.datetime(2019, 4, 1, 21, 14, 32, tzinfo=datetime.timezone(datetime.timedelta(seconds=0))), finding.date) # 2019-04-01 21:14:32 +0000 + self.assertEqual(True, finding.fix_available) From 186befb9221b2b8768facac79c9df1afff977644 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:31:10 +0100 Subject: [PATCH 07/25] :bug: fix nancy severity calculation #13656 (#13657) --- dojo/tools/nancy/parser.py | 20 +++++++++++++++----- unittests/tools/test_nancy_parser.py | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/dojo/tools/nancy/parser.py b/dojo/tools/nancy/parser.py index 2d4f4d986eb..8bc00c10fd8 100644 --- a/dojo/tools/nancy/parser.py +++ b/dojo/tools/nancy/parser.py @@ -34,14 +34,24 @@ def get_findings(self, scan_file, test): return findings + def convert_cvss_score(self, raw_value): + if raw_value is None: + return "Info" + val = float(raw_value) + if val == 0.0: + return "Info" + if val < 4.0: + return "Low" + if val < 7.0: + return "Medium" + if val < 9.0: + return "High" + return "Critical" + def get_items(self, vulnerable, test): findings = [] for vuln in vulnerable: finding = None - severity = "Info" - # the tool does not define severity, however it - # provides CVSSv3 vector which will calculate - # severity dynamically on save() references = [] if vuln["Vulnerabilities"]: comp_name = vuln["Coordinates"].split(":")[1].split("@")[0] @@ -57,7 +67,7 @@ def get_items(self, vulnerable, test): title=associated_vuln["Title"], description=associated_vuln["Description"], test=test, - severity=severity, + severity=self.convert_cvss_score(associated_vuln["CvssScore"]), component_name=comp_name, component_version=comp_version, false_p=False, diff --git a/unittests/tools/test_nancy_parser.py b/unittests/tools/test_nancy_parser.py index 09b3194360d..f233aaf7691 100644 --- a/unittests/tools/test_nancy_parser.py +++ b/unittests/tools/test_nancy_parser.py @@ -18,7 +18,7 @@ def test_nancy_parser_with_one_vuln_has_one_findings(self): self.assertEqual(1, len(findings)) with self.subTest(i=0): finding = findings[0] - self.assertEqual("Info", finding.severity) + self.assertEqual("Medium", finding.severity) self.assertIsNotNone(finding.description) self.assertGreater(len(finding.description), 0) self.assertEqual(None, finding.cve) From 99a1d7e7507d984fd2136a892269f1bd38047e6c Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:31:19 +0100 Subject: [PATCH 08/25] :bug: fix debug mode in logging #13659 (#13662) * :bug: fix debug mode in logging #13659 * update --- unittests/test_importers_deduplication.py | 2 -- unittests/test_importers_performance.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/unittests/test_importers_deduplication.py b/unittests/test_importers_deduplication.py index a006cc90099..2c607008720 100644 --- a/unittests/test_importers_deduplication.py +++ b/unittests/test_importers_deduplication.py @@ -18,10 +18,8 @@ from .dojo_test_case import DojoAPITestCase, get_unit_tests_scans_path -logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) - STACK_HAWK_FILENAME = get_unit_tests_scans_path("stackhawk") / "stackhawk_many_vul_without_duplicated_findings.json" STACK_HAWK_SUBSET_FILENAME = get_unit_tests_scans_path("stackhawk") / "stackhawk_many_vul_without_duplicated_findings_subset.json" STACK_HAWK_SCAN_TYPE = "StackHawk HawkScan" diff --git a/unittests/test_importers_performance.py b/unittests/test_importers_performance.py index 38d63babad1..c6d8652635f 100644 --- a/unittests/test_importers_performance.py +++ b/unittests/test_importers_performance.py @@ -26,10 +26,8 @@ from .dojo_test_case import DojoTestCase, get_unit_tests_scans_path -logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) - STACK_HAWK_FILENAME = get_unit_tests_scans_path("stackhawk") / "stackhawk_many_vul_without_duplicated_findings.json" STACK_HAWK_SUBSET_FILENAME = get_unit_tests_scans_path("stackhawk") / "stackhawk_many_vul_without_duplicated_findings_subset.json" STACK_HAWK_SCAN_TYPE = "StackHawk HawkScan" From 0ff017f9ecc45f910a9ad1252ee8ee6c0d96d856 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Thu, 13 Nov 2025 04:45:22 +0100 Subject: [PATCH 09/25] :tada: implement new threatmapper file format #13639 (#13655) * :tada: implement new threatmapper file format * update --- .../deepfence_threatmapper/compliance.py | 92 +++++++++++++----- dojo/tools/deepfence_threatmapper/malware.py | 58 +++++++++-- dojo/tools/deepfence_threatmapper/parser.py | 15 ++- dojo/tools/deepfence_threatmapper/secret.py | 77 +++++++++++---- .../deepfence_threatmapper/vulnerability.py | 84 +++++++++++++--- .../compliance_report_newformat.xlsx | Bin 0 -> 12986 bytes .../malware_report_newformat.xlsx | Bin 0 -> 11023 bytes .../secret_report_newformat.xlsx | Bin 0 -> 9609 bytes .../vulnerability_report_newformat.xlsx | Bin 0 -> 45807 bytes .../test_deepfence_threatmapper_parser.py | 33 +++++++ 10 files changed, 292 insertions(+), 67 deletions(-) create mode 100644 unittests/scans/deepfence_threatmapper/compliance_report_newformat.xlsx create mode 100644 unittests/scans/deepfence_threatmapper/malware_report_newformat.xlsx create mode 100644 unittests/scans/deepfence_threatmapper/secret_report_newformat.xlsx create mode 100644 unittests/scans/deepfence_threatmapper/vulnerability_report_newformat.xlsx diff --git a/dojo/tools/deepfence_threatmapper/compliance.py b/dojo/tools/deepfence_threatmapper/compliance.py index 32b24cde2c4..36b71a4d796 100644 --- a/dojo/tools/deepfence_threatmapper/compliance.py +++ b/dojo/tools/deepfence_threatmapper/compliance.py @@ -3,7 +3,13 @@ class DeepfenceThreatmapperCompliance: def get_findings(self, row, headers, test): - description = "" + if "compliance_check_type" in headers and "test_number" in headers: + return self._parse_old_format(row, headers, test) + if "Compliance Standard" in headers and "Control ID" in headers: + return self._parse_new_format(row, headers, test) + return None + + def _parse_old_format(self, row, headers, test): compliance_check_type = row[headers["compliance_check_type"]] count = row[headers["count"]] doc_id = row[headers["doc_id"]] @@ -18,34 +24,76 @@ def get_findings(self, row, headers, test): test_desc = row[headers["test_desc"]] test_info = row[headers["test_info"]] test_number = row[headers["test_number"]] - description += "**compliance_check_type:** " + str(compliance_check_type) + "\n" - description += "**host_name:** " + str(host_name) + "\n" - description += "**cloud_account_id:** " + str(cloud_account_id) + "\n" - description += "**masked:** " + str(masked) + "\n" - description += "**node_id:** " + str(node_id) + "\n" - description += "**node_name:** " + str(node_name) + "\n" - description += "**node_type:** " + str(node_type) + "\n" - description += "**status:** " + str(status) + "\n" - description += "**test_category:** " + str(test_category) + "\n" - description += "**test_desc:** " + str(test_desc) + "\n" - description += "**test_info:** " + str(test_info) + "\n" - description += "**test_number:** " + str(test_number) + "\n" - description += "**count:** " + str(count) + "\n" - description += "**doc_id:** " + str(doc_id) + "\n" + + description = ( + f"**Compliance Check Type:** {compliance_check_type}\n" + f"**Host Name:** {host_name}\n" + f"**Cloud Account ID:** {cloud_account_id}\n" + f"**Masked:** {masked}\n" + f"**Node ID:** {node_id}\n" + f"**Node Name:** {node_name}\n" + f"**Node Type:** {node_type}\n" + f"**Status:** {status}\n" + f"**Test Category:** {test_category}\n" + f"**Test Description:** {test_desc}\n" + f"**Test Info:** {test_info}\n" + f"**Test Number:** {test_number}\n" + f"**Count:** {count}\n" + f"**Doc ID:** {doc_id}\n" + ) + + return Finding( + title=f"Threatmapper_Compliance_Report-{test_number}", + description=description, + severity=self.compliance_severity(status), + static_finding=False, + dynamic_finding=True, + test=test, + ) + + def _parse_new_format(self, row, headers, test): + compliance_standard = row[headers["Compliance Standard"]] + status = row[headers["Status"]] + category = row[headers["Category"]] + description_text = row[headers["Description"]] + info = row[headers["Info"]] + control_id = row[headers["Control ID"]] + node_name = row[headers["Node Name"]] + node_type = row[headers["Node Type"]] + remediation = row[headers["Remediation"]] + masked = row[headers["Masked"]] + + description = ( + f"**Compliance Standard:** {compliance_standard}\n" + f"**Status:** {status}\n" + f"**Category:** {category}\n" + f"**Description:** {description_text}\n" + f"**Info:** {info}\n" + f"**Control ID:** {control_id}\n" + f"**Node Name:** {node_name}\n" + f"**Node Type:** {node_type}\n" + f"**Remediation:** {remediation}\n" + f"**Masked:** {masked}\n" + ) + return Finding( - title="Threatmapper_Compliance_Report-" + test_number, + title=f"Threatmapper_Compliance_Report-{control_id}", description=description, severity=self.compliance_severity(status), static_finding=False, dynamic_finding=True, + mitigation=remediation, test=test, ) def compliance_severity(self, severity_input): + if severity_input is None: + return "Info" + severity_input = severity_input.lower() if severity_input in {"pass", "info"}: - output = "Info" - elif severity_input == "warn": - output = "Medium" - else: - output = "Info" - return output + return "Info" + if severity_input == "warn": + return "Medium" + if severity_input == "fail": + return "High" + return "Info" diff --git a/dojo/tools/deepfence_threatmapper/malware.py b/dojo/tools/deepfence_threatmapper/malware.py index a1defd18401..3fa0a230920 100644 --- a/dojo/tools/deepfence_threatmapper/malware.py +++ b/dojo/tools/deepfence_threatmapper/malware.py @@ -3,7 +3,13 @@ class DeepfenceThreatmapperMalware: def get_findings(self, row, headers, test): - description = "" + if "Rule Name" in headers and "Class" in headers: + return self._parse_old_format(row, headers, test) + if "Rule Name" in headers and "Node Type" in headers: + return self._parse_new_format(row, headers, test) + return None + + def _parse_old_format(self, row, headers, test): Rule_Name = row[headers["Rule Name"]] Class = row[headers["Class"]] File_Name = row[headers["File Name"]] @@ -13,14 +19,48 @@ def get_findings(self, row, headers, test): NodeType = row[headers["NodeType"]] Container_Name = row[headers["Container Name"]] Kubernetes_Cluster_Name = row[headers["Kubernetes Cluster Name"]] - description += "**Summary:** " + str(Summary) + "\n" - description += "**Rule Name:** " + str(Rule_Name) + "\n" - description += "**Class:** " + str(Class) + "\n" - description += "**File Name:** " + str(File_Name) + "\n" - description += "**Node Name:** " + str(Node_Name) + "\n" - description += "**NodeType:** " + str(NodeType) + "\n" - description += "**Container Name:** " + str(Container_Name) + "\n" - description += "**Kubernetes Cluster Name:** " + str(Kubernetes_Cluster_Name) + "\n" + + description = ( + f"**Summary:** {Summary}\n" + f"**Rule Name:** {Rule_Name}\n" + f"**Class:** {Class}\n" + f"**File Name:** {File_Name}\n" + f"**Node Name:** {Node_Name}\n" + f"**NodeType:** {NodeType}\n" + f"**Container Name:** {Container_Name}\n" + f"**Kubernetes Cluster Name:** {Kubernetes_Cluster_Name}\n" + ) + + return Finding( + title=Rule_Name, + description=description, + file_path=File_Name, + severity=self.severity(Severity), + static_finding=False, + dynamic_finding=True, + test=test, + ) + + def _parse_new_format(self, row, headers, test): + Rule_Name = row[headers["Rule Name"]] + File_Name = row[headers["File Name"]] + Summary = row[headers["Summary"]] + Severity = row[headers["Severity"]] + Node_Name = row[headers["Node Name"]] + Node_Type = row[headers["Node Type"]] + Kubernetes_Cluster_Name = row[headers["Kubernetes Cluster Name"]] + Masked = row[headers["Masked"]] + + description = ( + f"**Summary:** {Summary}\n" + f"**Rule Name:** {Rule_Name}\n" + f"**File Name:** {File_Name}\n" + f"**Node Name:** {Node_Name}\n" + f"**Node Type:** {Node_Type}\n" + f"**Kubernetes Cluster Name:** {Kubernetes_Cluster_Name}\n" + f"**Masked:** {Masked}\n" + ) + return Finding( title=Rule_Name, description=description, diff --git a/dojo/tools/deepfence_threatmapper/parser.py b/dojo/tools/deepfence_threatmapper/parser.py index 3f5fd2a5a18..2b95a385f09 100644 --- a/dojo/tools/deepfence_threatmapper/parser.py +++ b/dojo/tools/deepfence_threatmapper/parser.py @@ -27,14 +27,23 @@ def get_findings(self, filename, test): first = False for i in range(len(row)): headers[row[i]] = i - elif headers.get("Rule Name") is not None and headers.get("Class") is not None: + elif ( + ("Rule Name" in headers and "Class" in headers) or + ("Rule Name" in headers and "Node Type" in headers) + ): findings.append(DeepfenceThreatmapperMalware().get_findings(row, headers, test)) elif headers.get("Filename") is not None and headers.get("Content") is not None: value = DeepfenceThreatmapperSecret().get_findings(row, headers, test) if value is not None: findings.append(value) - elif headers.get("@timestamp") is not None and headers.get("cve_attack_vector") is not None: + elif ( + ("cve_id" in headers and "cve_attack_vector" in headers) or + ("CVE ID" in headers and "Attack Vector" in headers) + ): findings.append(DeepfenceThreatmapperVulnerability().get_findings(row, headers, test)) - elif headers.get("@timestamp") is not None and headers.get("compliance_check_type") is not None: + elif ( + ("compliance_check_type" in headers and "test_number" in headers) or + ("Compliance Standard" in headers and "Control ID" in headers) + ): findings.append(DeepfenceThreatmapperCompliance().get_findings(row, headers, test)) return findings diff --git a/dojo/tools/deepfence_threatmapper/secret.py b/dojo/tools/deepfence_threatmapper/secret.py index 3d9f2584149..1915e4be694 100644 --- a/dojo/tools/deepfence_threatmapper/secret.py +++ b/dojo/tools/deepfence_threatmapper/secret.py @@ -3,6 +3,13 @@ class DeepfenceThreatmapperSecret: def get_findings(self, row, headers, test): + if "Name" in headers and "Signature" in headers: + return self._parse_old_format(row, headers, test) + if "Content Starting Index" in headers and "Masked" in headers: + return self._parse_new_format(row, headers, test) + return None + + def _parse_old_format(self, row, headers, test): description = "" Filename = row[headers["Filename"]] Content = row[headers["Content"]] @@ -13,27 +20,57 @@ def get_findings(self, row, headers, test): Container_Name = row[headers["Container Name"]] Kubernetes_Cluster_Name = row[headers["Kubernetes Cluster Name"]] Signature = row[headers["Signature"]] - description += "**Filename:** " + str(Filename) + "\n" - description += "**Name:** " + str(Name) + "\n" - description += "**Rule:** " + str(Rule) + "\n" - description += "**Node Name:** " + str(Node_Name) + "\n" - description += "**Container Name:** " + str(Container_Name) + "\n" - description += "**Kubernetes Cluster Name:** " + str(Kubernetes_Cluster_Name) + "\n" - description += "**Content:** " + str(Content) + "\n" - description += "**Signature:** " + str(Signature) + "\n" - if Name is not None and Severity is not None: - finding = Finding( - title=str(Name), - description=description, - file_path=Filename, - severity=self.severity(Severity), - static_finding=False, - dynamic_finding=True, - test=test, + description += f"**Filename:** {Filename}\n" + description += f"**Name:** {Name}\n" + description += f"**Rule:** {Rule}\n" + description += f"**Node Name:** {Node_Name}\n" + description += f"**Container Name:** {Container_Name}\n" + description += f"**Kubernetes Cluster Name:** {Kubernetes_Cluster_Name}\n" + description += f"**Content:** {Content}\n" + description += f"**Signature:** {Signature}\n" + if Name and Severity: + return Finding( + title=str(Name), + description=description, + file_path=Filename, + severity=self.severity(Severity), + static_finding=False, + dynamic_finding=True, + test=test, + ) + return None + + def _parse_new_format(self, row, headers, test): + description = "" + Filename = row[headers["Filename"]] + Content = row[headers["Content"]] + Rule = row[headers["Rule"]] + Severity = row[headers["Severity"]] + Content_Starting_Index = row[headers["Content Starting Index"]] + Node_Name = row[headers["Node Name"]] + Node_Type = row[headers["Node Type"]] + Kubernetes_Cluster_Name = row[headers["Kubernetes Cluster Name"]] + Masked = row[headers["Masked"]] + description += f"**Filename:** {Filename}\n" + description += f"**Rule:** {Rule}\n" + description += f"**Node Name:** {Node_Name}\n" + description += f"**Node Type:** {Node_Type}\n" + description += f"**Kubernetes Cluster Name:** {Kubernetes_Cluster_Name}\n" + description += f"**Content:** {Content}\n" + description += f"**Content Starting Index:** {Content_Starting_Index}\n" + description += f"**Masked:** {Masked}\n" + title = f"{Rule} in {Filename}" if Rule else "Secret Finding" + if Severity: + return Finding( + title=title, + description=description, + file_path=Filename, + severity=self.severity(Severity), + static_finding=False, + dynamic_finding=True, + test=test, ) - else: - finding = None - return finding + return None def severity(self, severity_input): if severity_input is None: diff --git a/dojo/tools/deepfence_threatmapper/vulnerability.py b/dojo/tools/deepfence_threatmapper/vulnerability.py index 3539518177b..69a01850e7a 100644 --- a/dojo/tools/deepfence_threatmapper/vulnerability.py +++ b/dojo/tools/deepfence_threatmapper/vulnerability.py @@ -3,7 +3,13 @@ class DeepfenceThreatmapperVulnerability: def get_findings(self, row, headers, test): - description = "" + if "cve_id" in headers and "cve_attack_vector" in headers: + return self._parse_old_format(row, headers, test) + if "CVE ID" in headers and "Attack Vector" in headers: + return self._parse_new_format(row, headers, test) + return None + + def _parse_old_format(self, row, headers, test): cve_attack_vector = row[headers["cve_attack_vector"]] cve_caused_by_package = row[headers["cve_caused_by_package"]] cve_container_image = row[headers["cve_container_image"]] @@ -18,19 +24,23 @@ def get_findings(self, row, headers, test): host_name = row[headers["host_name"]] cloud_account_id = row[headers["cloud_account_id"]] masked = row[headers["masked"]] - description += "**cve_attack_vector:** " + str(cve_attack_vector) + "\n" - description += "**cve_caused_by_package:** " + str(cve_caused_by_package) + "\n" - description += "**cve_container_image:** " + str(cve_container_image) + "\n" - description += "**cve_container_image_id:** " + str(cve_container_image_id) + "\n" - description += "**cve_description:** " + str(cve_description) + "\n" - description += "**cve_severity:** " + str(cve_severity) + "\n" - description += "**cve_overall_score:** " + str(cve_overall_score) + "\n" - description += "**cve_type:** " + str(cve_type) + "\n" - description += "**host_name:** " + str(host_name) + "\n" - description += "**cloud_account_id:** " + str(cloud_account_id) + "\n" - description += "**masked:** " + str(masked) + "\n" + + description = ( + f"**Attack Vector:** {cve_attack_vector}\n" + f"**Caused By Package:** {cve_caused_by_package}\n" + f"**Container Image:** {cve_container_image}\n" + f"**Container Image ID:** {cve_container_image_id}\n" + f"**Description:** {cve_description}\n" + f"**Severity:** {cve_severity}\n" + f"**Overall Score:** {cve_overall_score}\n" + f"**Type:** {cve_type}\n" + f"**Host Name:** {host_name}\n" + f"**Cloud Account ID:** {cloud_account_id}\n" + f"**Masked:** {masked}\n" + ) + return Finding( - title="Threatmapper_Vuln_Report-" + cve_id, + title=f"Threatmapper_Vuln_Report-{cve_id}", description=description, component_name=cve_caused_by_package, severity=self.severity(cve_severity), @@ -42,6 +52,54 @@ def get_findings(self, row, headers, test): test=test, ) + def _parse_new_format(self, row, headers, test): + cve_id = row[headers["CVE ID"]] + severity = row[headers["Severity"]] + attack_vector = row[headers["Attack Vector"]] + caused_by_package = row[headers["Caused By Package"]] + caused_by_package_path = row[headers["Caused By Package Path"]] + cvss_score = row[headers["CVSS Score"]] + description_text = row[headers["Description"]] + fixed_in = row[headers["Fixed In"]] + link = row[headers["Link"]] + overall_score = row[headers["Overall Score"]] + cve_type = row[headers["Type"]] + node_name = row[headers["Node Name"]] + node_type = row[headers["Node Type"]] + cluster_name = row[headers["Kubernetes Cluster Name"]] + masked = row[headers["Masked"]] + + description = ( + f"**CVE ID:** {cve_id}\n" + f"**Severity:** {severity}\n" + f"**Attack Vector:** {attack_vector}\n" + f"**Caused By Package:** {caused_by_package}\n" + f"**Caused By Package Path:** {caused_by_package_path}\n" + f"**CVSS Score:** {cvss_score}\n" + f"**Description:** {description_text}\n" + f"**Fixed In:** {fixed_in}\n" + f"**Link:** {link}\n" + f"**Overall Score:** {overall_score}\n" + f"**Type:** {cve_type}\n" + f"**Node Name:** {node_name}\n" + f"**Node Type:** {node_type}\n" + f"**Kubernetes Cluster Name:** {cluster_name}\n" + f"**Masked:** {masked}\n" + ) + + return Finding( + title=f"Threatmapper_Vuln_Report-{cve_id}", + description=description, + component_name=caused_by_package, + severity=self.severity(severity), + static_finding=False, + dynamic_finding=True, + mitigation=fixed_in, + references=link, + cve=cve_id, + test=test, + ) + def severity(self, severity_input): if severity_input is None: return "Info" diff --git a/unittests/scans/deepfence_threatmapper/compliance_report_newformat.xlsx b/unittests/scans/deepfence_threatmapper/compliance_report_newformat.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b0addbd103a791bb660ee421b1ce24bbe4e51b20 GIT binary patch literal 12986 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 literal 0 HcmV?d00001 diff --git a/unittests/scans/deepfence_threatmapper/malware_report_newformat.xlsx b/unittests/scans/deepfence_threatmapper/malware_report_newformat.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..46dc625a2c65cb620d5c655881cc28e32fa48438 GIT binary patch literal 11023 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 literal 0 HcmV?d00001 diff --git a/unittests/scans/deepfence_threatmapper/secret_report_newformat.xlsx b/unittests/scans/deepfence_threatmapper/secret_report_newformat.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5083eb9ce7853848e7ba8358335113be618ca782 GIT binary patch literal 9609 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 VDDbxn0ARv@!EmuYq5tjZ{{RDny%hie literal 0 HcmV?d00001 diff --git a/unittests/scans/deepfence_threatmapper/vulnerability_report_newformat.xlsx b/unittests/scans/deepfence_threatmapper/vulnerability_report_newformat.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a91204162dfa69c6c5357cf6e73b1a486ca6405b GIT binary patch literal 45807 zcmeFYgdLTEtvAVLrb#0h$_m+fEy27!_w zAP^Kph+}r&-ObC+&CBwMKg`b4T*S}Sh2s+hM*soB0q+05fBp~lKSqtG z?ym|zoUc+Ll-v#PrWQ0*YX6bi_0`5K$Ix>?3HHRqircTE@T31by(R>wF}kwygTFl*xgX zPAPOHd$!w{YN@M%^s8yHY|mT=O3!$aN~Z7280E?cbll1DR-nc4im(g6nt6`Cl3|)n?;y`#s9u zc1|96^e(DrQ$BEKc+D(g?=WpMMC=A11p50I7i94NDa%@;JKQHguIT_(M*@_kgRAJCvx$Wy%6rP>A2^;Y9zID2=72k>^JGmPMssdUz8E^1Vva@F-pWLFF@X zI)Tp6eCRLW(~>AyQeT{>-MhrCF?Oi-jdDojDH9R2fF+7fF(b^hOV?!a#d$e+;@;!k zvZ%^BX~b6QP-e(fTHz*{@*7#5ozV>19?uu{Gvz@&E}W+qOh%6#WF0GCGW0?Iw~Y ziN2U&o-uY)#{Nif!BsW-dzKBwv1vvOJ`DOcbg|dO-r5T{r&~;L=6c7YTd=s_xCepZkir}LG?BhMm15!LEP zji2e#0$4vh^!%8ST@}?v4Mw$_H8=23a6yCo* zBMjr6$aGy3H!=o&qlv7H#;5V;ja_&CB4JL4^3lb&C|iy}@iG)vRztqteq3N>%2-?F z3u3MLTye!O!?DT*IrS!WyT3*7asPGya?JY#r5-^!YjyiO;eVOqRU9R2sqY^sy^)gf zAQ|zhqQ(nx)1JyVI!#Rzd5m{N?6gy-^DKwgfl#}4eVy#!p?k@UF%5#On}_9-Re<1n zBpNjNs%7|v<_vi6_2VhIJ+9`;*@%7rOWab05!PwQP?e3=Ao$&h1@G8he>ih6Z~me~ z96ZVL>PCj;oO?9(il%52roi+opn6&UwYuir$6n6I`SRp)s&G_rLBMD4B2Jmbw>vs3 zWbD!VtICgy0)6-DV=+SKi&?C`+7-c6VvjM9iysBsu!ec*KKkStmMoUD`!b_@@bwqj zxHjQ|(*-8c2`1qp3i^k)N+0QJ|F}chLUI#*$2Zf5mTJaJlxxRmn}V>n=GGh^r+Dzm60=~V`LjuF zQC#;Z|NVh(pEF7F-?-4zj@!j=sUZPSmwkCW_krU9T*4Q-Wo+S^cu?w{t?iOs67r9p z&<3TH9-$l>nLpaST1y?p(6r!2gtu8!m5 zt(R+m8}84c@pHrd*+-5UiL?~R{6QldT>F&KWVFBRbq~{bt8MN`sQ+aDnNE;f0|GL2qE<1KWE}^dnpV->Fw!P)*^ySDPGNNE@b$plh>$hb~ zyiP^fL~yc8K2JjI1F?~0sC9v;b#WmTiyQ5d_Qnp;r#w5lV`=%3%w)a`UYXvL&epcy z57BR?-xyExQ}BkZmS9|qB<5Y7v*eH;gAsX32gPY#z+` zo@xcU>3vOa?Lg<~M^heV|Ktey&Ozy0intJ*yYz#VThob?=+@h-*ytfz)2*m;g}D8k z-qS|9Y3rv(h`cX71dW#;S7l?$rz}+4@K}D4C|9~1!A?hx~a8C}~u>}Wd7y`5jd zEt-yhMB%okI2d#H&Ef6mlwDcYcbc%D=U$9YPwiE_tI%ASvOpG`qib zK=i}M{%)esuWvs5IY!rBBvJWJcfCGbKCE}53b)~iln*YJ^Ep9tI5pj`AROb;O_Nal zg&)@fO?ZgYDs9}W+$og4qSmphh_|a^@;df&Pb?#;%LjV|E5l~c11XhZ@}#&yQ38^) zF9=ove?~s_j3Q5|0tNvGf`^PEj>$hgeeCW6H&H&Zm8nzq%fAdCPI$)?m%b*ZO`q=D zz#`z#9X@&|v+fp+J0=x1%t)(g9~-IH=v}nK)B8fnZd_QAq)uRcJ2GL`dvJWafgm*D zxA$O!u9qQQKco4l{D5rn>@Mv@uhsqNzyEUe-{oEQLdr@{a;c@d6veP$sO@!+h7D5k zXZZ`mQKinEoe$Z!vnAtonlNZ)hfv+1y=1VhrPj*9FGwd<#wMf6^*QD*X16Y+_4;VY zGWhb4{qNtlzXyrRfA3r!L@)i_PH+3m_IF#qb2D1`Y6y0rWIef$I2-Q?36T8itleKf3;ndF3MzEBapFz zi@;|@e11Ko;X5ViIL=X_8WN>Cagg$L=pZg_8NNg8{v1wG!BaaW>9CCVIQ#^OpGb`n z|2T1=w5PqYq4N0vZ1BPpEYNnd-nv+^EhTjBJRCvs6+ib0*-czpN5~d&@bhcMbIt6| zDFj{!8JonpgR$H1xR;XW`k3%U@)N@fx%$vAiI@Ms#(RJsSdzI5mzx;T*Dl%~TtSA4yq3@0zJN+x~A+`;n`dP91Dq8qn z*yTI@su*x^mg@}tE8!?5ZMSC_wL~$JR_ga=*BQn0{mh2RGmINnFP=45iGHf|l@i?0 zoY{zcZiu!=JU+>->A&r8vol4<+Do@L``N6=yB_1xXuV6M*f)0kaQr)2xq_nm>efSw z;^;tR|4v;9!LHU*U%Px@6L5M5?)d`#XpPjOrjwP|E?w1m^a_?M8>Cy#+z4of}_1LWrh7m2h2!MjlR)#q(*`jQT>Wghza2-0$a}N}1ol2ZO(nO*5UsB5s>3lL@n* z7?5Gz-JY}iEWWeWo*B2wp1D|YE?%T!V>EYf)`5E_XK&WYtY>XZ5$*3fwzkX5WN7}0 zYC@KIBbM(Kf^JXcm{!&S3BJwTlH@#b2e3n!)Q9o$-rdgi9fy&acP^~g#tY1j z@-i6&Hul!S^6kt&g>5hAWS19-j&8_1QW#?(?glMJoM!FJ*%lqgp<~q5OacrkbO*Eg zW&!26i|a-4#T0?V@0qL;&p%$d*tt26AIZua-xnV4!W}$xx~Ewp`f+&eWi3bH#3rpm z;G1c!tbBaNVgMFYJ$vBOezFaQu?Dwyc^sNM!cIEQiS+M0<^>zj-ri-P{|n1^}g zmsLyB@ubLhn19d?)bltk#oKCM*6t&doV89a+_+1>cBBM5HWQh|8;6~;L{{>KWm52! zwQ83HBym+bTya&>4MG(3rAD`D6(|CBTa#T>D;DY^pA)nU!I`a1$V|m>r;kCs)OFIx zGRY15?{p_K6vr)@HDB#s;W+1IwNr-#4*(8BOewMS(w)pk12r{_396?G{y^J^ zi$+A0rfz4}xQ@Xcf8xnrUn~_E2`6&9Xa_8oD{jQ=RX9x8STMihG5h6wA4uxE`{Juz zwKfuQXheEKR?@Dj=A0=em~W2lB#mG5qZ_SKpRwq@G>HR7*j``Q`dxeVOyQ%4Psw%p zW1Csa=cm{Ebkmn&bAmKPU1d4v;u4Ux4tqMtpFuvoIjz%igH&^IdebkxzM~P+0WB{o z*-mCNxo5CfAhjZbH;(@l_O}r%keh^>x)I22H0C@Pj^ldD&F0|K^SxuMHM@9kF6Jv$ zrmr7f?>#aJfBi2fYp+XN=W(X;_Kw%cVO8Wu$1OPMXQ@JMd(1uhTfe^9lb~HvT=$l3 zuP$pyO7)xCFKY;z?YaHc#mnE!)^nS%2P1l$oIU4ixh~_HaN{Z?29H9`sd%=9hLC0C zX_Zv$6O7{C8 z(O-*iz&k0=QV06q7kZcJPt%N4p+6g$Z<7cmUaMOL7Syio0bZ+EP)rg2!!!4zGXKTi z@cJI$Gx}i7i%m;#;IIA^S(v6G!ls+<$heoq5sjlWv!&j%wT71^dg{+_3MP{~R(3f)h_?kMm@4CzZT35}gpUuc!0Edp1=Jr(92k3n5uCtO{J-U$-2>!XFfPN&mYs~~p zysX&(6>{Qvf8G5GAi#CpGvWW9M;?HEUR|N+TOd-u^s4Y!gWnIVahUzGCvXi+j5e<6 zx{0Xc&wl=9D^@WVyg&E*eSyygPsXP9WBnefvHd%zAdPbYX>1&q^cGr@u zLEB6&H}prl;Dl*0Wvf(BZ`uU_A$N`SacYY+t9SueH|w#ZmO=XmjIF8v0-I4&R&`|^ z&iz&~rNfG6RsO`UCFidX{Z`L(-|NB}(oO{Lf-?Mdib33m`gL zY0Irzb005=%znA^!bZ26Y6Pt@0%k3QCTF|g^(Gmti{Mxnl{W!Z_zSbs#LZH#xlo6T z{t!_rY3R;jw#*ucOd9ISNHbiV@!A-~IiazYV&EKIxYz;-yB%{{|q(FC-J1ixH) z-`Dc?qS83%H|cvUnMzwy36<*2YlFR^YxGzDo~bR2>!z?;ob$K@zl<-YMW_W@#fxVB zGyiQUD^sLR|IcN>)om+^i;pf*LJf^EKuR5^$?~t%erXn5Zz7y2JW7)#$WdPPwqxx@ zCr!QgCTR%n!Tf6A7OLT2sK-UWiEzo~dokxDcz^nZ(p0@`PG1jMu{poZFkHbJ?tCPU zGA|Ny=;AfR`0`}(IQ<2{BW2@wKcTYe#C7;-yGV@WOe*Ljy4iBpcf{jk_>x^a^$Nvz zO~vI)QY;Cft>R1o^x0A*?K34D z_Kh49w7Kaw19($ zN@(9&AryK`=wi6rD& z!U34tK6-%sFy-wJWTSuXsc(2-k&&s2;%dP$!Cvu_f($NFuuSDxSGl_csXw!US8U{1 z+t_@hF}{WO@z;r+QBIO;;gaEd-!1W*!u~l+5y;jd{?wlfNhiAx4l|t#m+x4kZ=0;- zy8zUb?GNOmJO**GVxAemGl#tbx!8=+X@O=-FI<7qeigm?bnY}KGc=X~0VD1!hxh}1 zZvOJGr0GBLXO8`&3vZ^<*m~&>XGej$`+*T^8Jm5=#D3EGG*t1obJa{eYSv%aAmlr} z??9`9(8|PuBIaH08JF$C0>Qjr*CDsb!wOwRA1A5`-ImDbn$1Jw@_8&fs=A8pY=@FF z+^rglzwKl#PUif&WG&_z%dF+|(lg)V_1l+>-S)>m6I--qz@CAkE$|IrTO&Cg1wNzS zsu^=>vS@I%%-{Vvnp*L$?~QAoMS$NuWMsfLt}n{pqC!_5Xau#6KuMrZf{OqSkl zCE|*(B6Rl_eO2(Wj#a#@_4TWPsZihDsRBmf7P@DNE_jnq+B_+rI%=e>S%UaOjJ}@i zzAi1fz$;fDS)WrZXXVEf-ZGei)CU9+?8$5#vRY;C;0`GO)JifxRbxyWx- zaKPbXaf>1r5bElZoYhbK0~2e?#s`3c7HCm~!7T(zPQ&M(U0F9@(jqBNHji<7)1an{ zIad#u4rk}E=@J6dI9y>XZR=OL@J890qumdu{r6=S zuS?Mu?Vdi#w@`g%-*`h@_j~u-g$|_=y&~>zO>q~Gb6cd%Ep~RmX1^j!DdbRw%6&e3 zW;SdNIrCc8iPmY@1>`ZtMNb#scCi#IuOG=j0B$U~!7H~}TwhfD2Gg_r4&J%TKl6fo z`s?0w?awQ8o?KZM-#|)J`^R~_$yGL3fFBHRC`EplXQLpJ-qTJRO2PP9|0Epm1(BLr89~s*_HkYa#hr6J}IzZ%^0awb=bm{xj1earoM02KXAwv2+!%r4>_`wEf{M z!%yFjWI}9&NkXt#Nw=lZ>3squZT z7XQ^{*<9pv?N3900zY+ro=6Kdr*^I~asr4wh%S%llM$SM#R!+6@>-Xfvm^`(bK6O= zr>fZA|NJ=KZ+96;>fdm-MW~Am-01RsWvk$Ib^8%%3z3lW1Y>1}UzlFo-wq`|k+O$e z*Z+FCa?$YC$-^AH7Ubrt<|JZD3g~VTi}quR9Sx&X*_L=|+~v7=X*`E~l?ti$?QADA z67oUG#sHc3f2RD8lb;+h9+XOK{#(Om>j84J)n^N!X(viWdqjLG#^tTlX#N(#g1}Vd zsiby0+wqKeYVMi&?6`PS)j({LE5Eewt-jV2yTfwl18jHY`%iZrRV=$M7!jAZN|d(l z-wD;1%1&4JyIZC4(!MBdrk_h;u_CwzQ$4ZyUV$WFceFRLG^0i2JHWPvb1}~wfx4tx z)S6tVcrCo~ee#z`tH&wRnl{=>mu5<6dk*D`{A^kw4^kYc*g zg$v(IuHLpkuN60JOAHCnRtXB$r*=34^smHh14pgd9s(S5E_HF^ zKQa3K0Ew8=1EvrWi|aoWJ7cOBn5-!R?21}|ai*doE}8(2{OMK42Q(|wnLp}1jbx?q1Oxuz4h5>?wv#YIUq{ct2>PGf$e5WS#esv&R9-cLO~DXuC8`5 zcw02=R28+7DAXj$IrkmT2e|lnIcK@^;(NAKcG+?dQj*2$T4#?D9yn~TEo4$({kVU} zh;>nYZ%QmMALw)F&)COeum9O{r($RHSly&05hhg+X{@ic60nCq9ii0pf?(-W_2aS} zL9>>gq>~^;)P)v33Y*wcE7$|S{AX?7r8xDH1vsv2SMsje$!HwOD;u(>nI0SCgOjUK zLM{IlngN-MWXTq+jY!1JsC^gEo(5zvQFpCL8Y^qRhF1o2o|8`JoTwZlv`a3fo@29# zx@8D}tRsmH6}TS1Nv;>?c2;XU5ZGV!>9cX%JE|1}GF7ge1k(|VVk@>iaz*J_nDY#1 z|FRp%I}~n_>A_Yi8^Z)=L+3_jgoH5qmm2Ra&XCgIgft$pzo3#+1;t!%*5#k@s(h3@2Q^R9E!aW$Re zM9xW-gI*8%^M9W+1;BMi0ILvZ86KjQV+tFgKn|G=gPn;c7~z;yCCrOfyHrlIVQq1} z9=a2N&K?N?2AWk^xBjbg@jbkk^VP=P)PUf>E+#LP5NSJLtORnXYu?Ac1bWG8lvh3*%!W5w4>&0=zjBq!c-W=M<-bZ_wRpxA(-vO$ zR@hDoqy~j#8LM=+j2afzd!)Ynm#c(jvDGTv@UP%f0>j$YQss6(tDT8Y7JuI-ldyA$-&~ zB`Euw4ns6pChIu;siHp0(jz{kdcEY15%~2&9<&0&yE}Ww^X@Bm&TE0H7p=7^AX3y2rFXY_J4=2c?@nu}hk9E&o*f$?q z22)O{xT1&_Bn`~`q%WiJFLj!itz9`;*}Qf&6~;gpjhAC+xST+Cb4(6fVykm!BhHiO zC8dz5DQ@LV#cnUq_NbA8e84?l+VPyl_}08|rKj$Gp~o)+;T@?uDy@6yUUt2s%%5Hc z88AzJnT5b;;^}Yt1V6loDuNbHqyx!m5q3{iT@Q&Cj0`e^faeomy0k8X^~^uvpkj!7 zd}%@Yba+NH7R9KSO&71*6U^~`{QDc9b05#Dk=UtUf1^Bn_M~Y<`pK|8hK!;#?eUWS zfg0Xw6Y=D^Y8x*@gH=3#e|RPZb`02Ft5f7AlZMAq7mFD2c!Ln?X!De}QMr$(hWl+a5crm-7Bb@&WD5#{L_NJFU`h7gTVEx44 z7v54_Puh1kh*5a-@P>?9s66@tfhIg9kfs_JS&nbz3|Gp9`GvZ_)NBps=i*rMC3L>B zT9UNK^akC*_yq5j;GVI5Z?w6+{J6342Q()gO*p1*U>0@nOkLHLX>p^LFiLL<)_AkW zG^;Ix>p>rOa7y{%s^jJgfx6@xgh)TXzd5Z2npfxWf~uCRlw5J~;YeiZ5bR8=ad>5t z(*@m!WEU>C`>L#U`?0>^=bFAl)?1g)h1}2J*bt_ww8h zpIAq5b4q-0m+{_`8dfpj{U116qx?N-$9;*Y273zhwUsGz$re~5mA}WfZQ?oh1%ReJ@a} zJ7rH@di{&M%A03Jq9mrG8!0kua1DVHocDX;_qKV>tpmiSE6LUf>KLs%rJipvb8Xa+ zMb~P3KS=3_awWX^z!M>2?Z&b(EeI9uhM!KhPgq!;=T`IsoSSuV(s=a z$czdVry4KJ8-|Bec1y6x>2_{jHaR+kT)TjBgvolRp0@TVZ{(J|F#l;7p- z(*@WY&GqyCtPV|3YyT#yT1O>yG7Z9+D2^d*BdeG_@-Va5g8b4wl;rmB%^?W1_Xq|&Lyh1$2P4jXor zdUilY=4BeiO?tHK7*T_=6PT;rXM_)c7(9j@t0 zQY~L9mvKgs6EZ5SsZeLcdlp>%$;j_@-kcu#knKGH_PQ3%)pOpQh3$ToQHG>1$w`FH zO=|6Uezzxk(49;;sk6j^Ge`~IC=Vm+hB*e`$IVVyesI<|NdAUDCN$xbKK{XJ0#a2C z6GQkhDv$J+F~qKu6DMKL7!oF(7HJ#6H7y|ij{W&lNN8$x1l|&*YLU52`R?PixV|T*2z8-h?4xkwnm!<*zlE+e^jN^GMaC>vO+vA*7y{~`G|Nr-ftsnbDeq4?)sD6TIRcGKpNvbv-K z8~P57cusjADi^FZfHA?#gvvuoJBpP0V&H4FTyg`OVn=DZsP|>`W7rdi^?znF`!Op@ z(`dw8Czr4Z@&y8ZK!Oc8us{o4N8XrdR(VAs?r#yC>KOprb&A-Pk$7braP(xPG4; z$1NrlS9+@6MMh1fv7xIh;XcGWo)e`5K{OFck#-hb0J+8#MZoY3-K~h<;i}6~Uqiv`7F@!yJXTv@0YBR}1TR5Ge>pE5ypZnV3a;Wyw!6)a&=962pk5pY5aR#wh z;)TZ@I!gwsMRtBFUlt65OQ%#}7vF-7=Ywnx*x_=;w@D)uV^FIE55z9{s8qeWi? zOiL#u5$;X#RxJyp>`~9aXn-P8lVkmf%c6V@P2i-G3m2gRm{ZIQPS;}oycf^gSwm94 z9FZMdd@-23l^}D%O!x}&ahEYRvWAeX0Xl4Eu>35zFoksMX?ir?D48kr(VWUrp7BN; zxRMYB4cC1da#=c78(v?_R5;%ky94L_lkax^$*b2r7nZ{$6f17B@^Np?>%ug%! zwD0by!n6$Xa7cBOt5}_*{yD`1a0^|ag-NM)@^u>jHa1K zC$*=}ypXz~uGV6b>i-55e>(>G^<6tNQv5?N+Ij)T7e4EuwyiaA8ICPr4~6f3V@5u2 ztU5ht=F>@GG%Xz9D>aC2w;{fWtl%W!`}d4oC^&|#%26gf1Go#uvmo9PwOl`p#dsMu z>v7(|9Y9 zyjTEnpx&NA=%jDB(JGO(Sxcywo%7r4`mA92Q1#t~97y^6M&*Txv~>h9Ct%lPglA) zlmGeB7L-F%MO+?P3YK=U-<%%IpRUBetro_7*7jw13t-Cf38CV&;|cl0=6HcD-H(e_ zWXwbKX)d#m4Zv{?Jcdc`O$0}~W%lh_ISxz-Vs7%GI5%7uwDW$=2(?eeyYK<=?#`)79;iWLhq_4*l^#_8;d(a^1(9(o zu_C?7gm<{z2i0Z*Nu{#ON4$V{XH2>%EaNOsqdgn)lv2qw!NXX*E#jZDBIQnM>!(6bKmp( z8LMJmo*EakG52PxJIBJfU*26LUqsJ-moKaAby zm4#V~QJ+b2dx&S*SRH>f3`ppGF`xs$s$|NZ;Pe3#{iVjIjT0t)XXkCd$``&UlTRE= z77;v4Lph=Rf*)Ah#^^6KfGH{)T1%D);p>8dK-k5uxW(THzR5B01f+P@uDj1UDzD{BbXLV9uE1u zm@4Y{p5Y0VU~!9Ddj_$SD3Dm;iWa|Ziqd7w+XQXX1R{y0n$>;>-w-NXU`l_K1>8mX z!(U34j((e{#L;(p7U{03Uj~R#Y+5~5#io@}Ww&n7ju#Zb%?6bvDq>ofC!#48qWUe# z`xUC;IiJI4CgkDYoO~F89$#)u)94InQ3r$+4cMNkm`pvd2_MbyHZeO2$f$?7K39Ya znG`*BE1CzjY$yegp2VG|fDHWU3RdYpxXIln)E{2FoN|qOklI`N(&(9Q|) z_>v=F-!*FU{m@AFNXK?4twcZ64uw>wneT(f1p2=~Ia=EmBSiGEJwrqv3;2cZLS*u+ zKQ*5@US`L9AeZWcMv5x?=ny!!wwg11L5N+p>cMPcdxC416C0PGG5kiJ4zv!v?9Kzf zqc^RUL+RimGEI2ArrO#155A3w0hK9CIv~cAV~cY%AO;B;S9Yc5dUl+ubBEctTNM2< zpL9MbylR;e+`Zcr@!w1dPU0EE^x`T~eG3$NCI#E()R!u(O%XSm&HLr;fEfb{=C;=}xFAhqJkFqsJ z1!mHgO@LBjh0IfiNx1M209phcIDS^3%-`%W8W*Jq1?d5V(GDjslaQ`Chdf747|=uS z34aOU`F8|QsdP^!|9Bppbt z%^MHiHGC3m4Rs$7+4=Dw6NTCXg8=akCibiQvRFXaVc^pUBb|@K=@<~8oc|Ni&a46c z5JNoa3q;a~bR?iKTrpw!aghx+g^laZvcv2JIhNk)wd=Q5>dgnOgfeSB5QCM7B`55b z`5v`UH%xf0hR=#)^8iqXd(iJ}LkC~R#IwrVN>nUy0$4+_=29^z`v$84{Z}2k11~Nl z7iBB+gKIeXAW!r!HLF*Xb&=O4Z>vM;;WEKYewRw zr+5y3dx1^xb2+q*2(bcES`3iV&F+>o5=##F;wChu%!6tMfl&^MW;8p8fGt*$icz(j zc@7zz=MbWZ__RA`#pC{%#R6q7t~vpG6Y4btiBGVRDptIYZIDA)R{HX?>jq$zL7$4& zF%gnE1Nf_fe4xFb>Kn?v5b5o~Dag*WxauxrA3Tr`+gqPWXW0T0ILXTwXwd%|Ukn`( z<>Z8r6Z!Gs9Ki!Y+j`0~%sz&@jQ9$TAFk!L|y8eXv z+OEF|g;FKCWvFcMh2vF-cX$@r15H3#^JHI;#Eo5ndR`0+%OwNxGu6xbN-;lZOCx-P zAvc6!s7+C1D-1fhcSGsaBI)jQTU* zKr910Chd>nh9PV+0YKI zo&?O3)K#lJxaI3-7a2f?qraxKkG)E}ouP&Gq9a0z^lrIrbUli>TD8dfoYZnaIY?vP z_s#b+UNEV;s2l;d4^9q2!2}|VJ2nE$zzGEZRX#CL z`9s*szc$A+^wujB#F7?Ri({Qfb*i((0wOpPlB{)`zcrDS8CLZ7bv1%IhcR9iBQmTf1C0oO}9N^cNgllW5gS( zPk}~jm8@XxtCsn1i~Zx6)&R$(i^`5#KAvrpJ7-_y%n>5(J!9p%`ev(Zz#KJleWl`d zz0BG`QAg(VH_7X7r@VYO74?^0wRV%7;$5}8z?^9t{#LKqn6Xn|{@%X_3Z@& z2X7+OyGhnT_1c23HhABxI*%$*w8MW=!WuIus9-2$m=F-fO0Q8QIaX$$YWK6G>(hOn zv*iRj0Gs?_!I&PPedtLrBstFI&VT*GYf?3uV#T6&%U6;3CkG3 zYL609HxWo^Bhs59i=| z;GHp6fC6M3y;M>%Gg*ZpMX6d7?#XL_8Ffrhv!YZB& z1F*agE$Hb*B=H-w1;Y|7wPvhZ=Aj=?1Lj@yJC&p>iE01+-z9F=*`t)nzL8$!E}!>T zgj^2&g=2QlEJHAX3TO&T^0BJ*Ddnq`A?2&NI)Hn%TbT3j2^zGzQOD$-Br8CH0dM%R zU6@a4ZI3rFNpu!|RlRaM11XfmEJa0V{y1#Z2qN9AgST=&*Y14xa^f{dL;x?)yeH=( zfOi1O&U{wKG5Br?%U!t>BXh$G_ZIXYg!|?-MH1!KK1<}Ld2|``8eT=&cpD=1rFH#y zA9$5<@AB%$As9bRZXu5{jom5!D+)bv`}c~LFU1dN%&*p*kZlcu^1`a$pE4-%+~?It zHUNP%$^WoZD@R}(`hL&l#JXVQ}}xQuxS^w0xnceIl7;oPY&*+%pZHx!XDG|2G2 z!tBBC82pFI9xp-2 z8<|$Gd?0@)nhZnU6=+c_0I1+HZvq9};&9u_)luk$RHyr0 zRUH<+KT(?3D^}F{61bsbu}2C|;$u0Feq zqi-Cy+E2>gsye(b2Qi$IkLl+3&fJa?y;nE2z^||iAgd^| z2QV5skC?79{IBjlyeSUwDJAX$9SE}$_xVkcgvM{=3qrrd4*HKM85dd;H1y;29VXud zZk|WXfUJrM1*Tkgn1E{J8FiE(cb?mJuvB9Rv!@0xwZ7p9!a(pEK%+2D|a>lZH9@%YBQz$VMYC&L&j*mT#v?983bDx7L)l&p74>2Rp-tC zBaF!+sFs4#xoud&Zm8+GkBZ{5|FvL7uM3!DY%2a-y(}raj33h<3A`SY%kHw?4QAaP z+X7C3x0*Rdc(ZBM{RmJf^A-=#ytwj{Z+XdqvEOIsvOYEU=>Z1dmD9T?d|bu58aEcf z2Q?_{OB&`P?w$QFC`7JQQ2hHvrn3QD56rgNDrSUdBL@HF*#5{(R9&2Q8|FfF{_lC( z(SYrnuz};S9;rXL<7^+tATpU0eWU8g9J_EupOs-pX}zfm9sDwD;8+=;paCBDRd#}z zmhK>fNfEt+E0w-{rdVL$ruP_Nt2DqAT0)MTsx(83Z@Q9M>+HR?d6VBZb|yX>-#L3N zchLa_g0qV8s_a_OP&wA(dqzxniqg{9?f>pw(fhnz6;pL*SL(N=d_0Sh7<61Oe;969 z-|ff(xMlM)kXptxnfO5HW_7LudXDDmB$SEyJAg`ErIHQ3zI^%3hfbB<`~8uuq%t$% zC6|J{-YZZn`6;VIULbj~GoAZMJj<{nFS)b*w!^+uLc2Iw(mqqDXIlLOXFz)F?q|Cg z3HHM4`Mw6fut2$fsAf2CeaI{E?t$%sKGL{2UZWmKxS+|1Q{d`x&6UwS05GO&q@0nF0Y=1Df|uxL5b(MaQF8oA9l&W-=390QR;!#P1#?sM_WKO1HFoIhJ5C3b@HBn9fEqA?d-cIL;u?BTU>q z-T+T@@^utKv})U?DA=hDRFaUdaX5zXRgwHyxvhoW8alrDB{}qM_ zNt~f4m4;JOF6q3chqiW0?Oa|UT-(S%Z4OlGW_SgZD#QA#ghRu$J(a1n2$ z3Iy@H=WzfpDud8~eM572b*@At6j0yt!M#K^adYWW+a5EqM?hU;)0u4iB{#Dkz(b{o z7oZ(%)gC(%%)kUAMkO9GMdeorK03u{t>&gks@f3;XKyT}R5cEWxde{j`Bho37Q;s%y`Xj%mHzk01Ego#RE?*Os0qQv`>tIqY&SV7ylmsBc!ELF2 zPugTAacgS@&UUG>aw?S3VI7zdQjJ@D!cKykf8588VM3M|H+)?!G=WL@Yj=~-pe1&k zvo9PdcdlU}j)g7FaT%8(oO|g!N3tWSZWS(*bx|&pMPQU05Agz|5_S3rHSEX(R5!$m zV5nUj99ls>KL@zdth*XzT*+_}mH8{If%fOsvs_ zTPbRbFs|@Mo`3nk8}o0CRvIu&`4MGnENCztgNv22a2=0#3FZ(1t#>rgjL@@P;1liS zU$%17FyavpF;*8D;B}de+=dr1Me(oW!~)PzA@0%cIpbC894??JYHpN#|QFBthVNb|D>bRv#L@r&Hj#%j^kpiuOOQKRxC~jyvh(IW>N&O zdoI#N#!%Ijo_JwBLeXbAZ%hOb6s#;4pF(|dhX_!Z4DhTn%Ua=gf#Tiop<`bYe5n(M zjB}q$VabFU{MA2kMUOK`hgWMZfYl^T2h)!9KuW8l*nfa>KI0r+M=-R*4MEfE5y1O5 zj?^2BzzR|h7-_|hmL(vt_?SyR?EVRiYE`FcbhPT&-b77rG61ors4E^sm_bpQ6aWVl!Oae-msS z@b6{pf0O6##?CI*_zGhf%<`uwILC?ny-P7jD%-?PFdW)%cnRuPfr)8T30nZvKpg70 zRiYL`RZMbT+3KKlyV;JO1#8QQp4wcxlcK)_8@!G!<4FxMCLC@-ehVBh57J_)L17it zwR-4Bp~>n-+GPI_GwBX3q*_lYWIhf&93oSh$AzN;PiRE#WdAK3OS&mnI%jCHOeEB* zF(f3v1_CI7-a+J`#}x47J!2k&EMNt5+M_* z?)q<}y2wP}yqn1WrSJUprr~{mM{d_Uqrlv72Yu$4F0bTv8SK<-P*p}Y@}7kv%tCFp@iQ!0t0tCqmd8cHh7S?$g9TVR1Z@%}|B zd57h0tWF!ldPw(|6Vq60VlV``E79#%F+@G+5V{e!f$3=B_@lqqVTVof5VW79}R(kv=~ZsVnY1i zJ`@k+2{q7Sh4cC_Q@;@QC^*=$Kup4W%`Y(pJdmP~cc#vdqMQ%4ZbP#I2ivkOb z(#h##iR|T&QFO!@o#Faf6Z}*2iXn?Ha`pEy@tp~vdp@iE=`A#=k-49=5NrR2oL=jg0KAol#fZ(ootGDcr2O zI6gH`59HuBU1j7Wwthh9F^*?^f|8Hs(T`>9`f&&4$V*SUd`>(0tY;Iy>!tmeg ze>sgyjY(S^4%97-Qvo>B9m&S0`slHM8i~yjc@QnB1Tr>}zl{5EkfbIOox~b0%K3tt z&dT|n^pSyIR!;|Zpc5s@gPRP)5>`sSP`y7=2(9b1F@AM%ns^m&y$>+`hCX zEjkt9K$!_`1I*b2olFPNiML}#(qnT}uX5%lMVdCM$f8O+GKwI!b}^%*ecKA{k8bGG zAE@}sRKG<8-x?fa=wM^uDcC{)EORBBJVH$eA_7D_c>9p^FOo^e(?E@Xe)M4Szo4mzd#iLeUeV}r86VC zUUe(!M(_&XOM9Iw=QhiA zuAcTlI~6ytr__*@{PaZU#=u(k%&e_pbJ(%;IUkCyt#U+79H$d9n9723`laFY5=WgH zvQz#8P#{V|vBcusY!mlrkn(-{LgS$bH^D+o7zPn4lv(Yqa)ipPTRori_pDf`^^vi9 z)h_=CbqghLUL^a(@z@_M_b2$V2eX-Je11N!*NwFz2{S~$jF%c64c8CKe_TbHfbZAK zi=MmR*Zbh)=+2=I!$C-d-`ztRgYVCQ%C*6e31zO|$Kmh-#Z2$V`Ai-@PuGnjEAyU6 zqW<32P}w$;jJ`j@xBJk}lp!XGp+-KYH;?vIyWrWW(< zQpyy{0jkto&rxzr9&xug08MQe>8@;j!{arQ9U_vMWT>$*nvzA+FXXYwDKUVO&G7MU zZR=>c)gqs_A&G$|;W?qe&?3x%3u_9cLpyF^a3-*TAhT-%Z|+^+&{IgTtVB9<9f2W{ zHhU{)^ory=yaJ}%I^r2^LxOf<#N-LE;gsm}YB5d`sgonQg|(po8VaT2;g;zRowkZ*1#<73aP#{|w{7L`(PY|% zQh`v{WOv7QJB1a;wUO{yL$;zf;p4VWUk55ez9Iqj<9H+ImVvqwO+>vUI7^9gc!okG?OgE06O?b_i9`n?FUcW~zUI?I%XmOUyM)yI z-O_E8Wq!pn;BQR}aNzBBqE%3Jm)#-ZnQP*mdo-v;>6W=-!Zd-Pr|&(bjlB#jJv zh%>?HUIj;?vTtzy5Oz%_jJ_n8_Rr$Ic_`R53oH@Mg`I^#=47o$LAe#Bodj#toVOU$ zFmIR@65Z)(88O7jg7*|)b_ozm+}zjmRO$7tR6}eQZrX0DSy`wm*P7nbDYmVzLSKkp zrQPw=Ae5}RqHB(aTIrO+X}07O1Kyvn>T0T}9Ia-eZt>i;RO%Xm144rQm(77@8On^5 zzT-Y<%WSfq`Q?7hQJDYz@j8ddaDNPJk0#4e_bIqm4Wagk$Y%b7Qj0a;{C3DprqFD z+s}mQVc#UEsyN)A?KHX}*{(N<%mZ>xb$e#hG-!TCMcX$ubGo#j8p!j3N4B|=VB;!R+AvgP%HoBA`lM-`JAZ4mGVPaZRLchhA^ySuk z$ss}hvFCy%PosrC9(P0|O}aR;RGAv&?D}TQ#IZ}q-bx1zC>kGtNvAtbzhoNz=ug|5 zD=gc2;+e&F${Ss{H9`&%a=;SrH;|c)c+gU<_3-?#>&o) z7Pe;pjqgRD3&#WT#3w(AS3E#uO!K=_klQ(X3x#pTMv;qN6J4{J=?aczJQ!89q(7*C z$J9T(1o7Sn$roKGv2s4Xm@bAk#oVT zhu^%h_R6gdra_|-%G8rVenvcYH{-lKooaz8+jzk^h4J88x3Ff#5J z_WaJXO&$B)0rTszXzzP_+HJ>p&TZ##r#tp~Fv1PpZ(+LkqxA30;{%7HxANB|pDua& zOm~z+V(1dX4q4Ky7Y1!+Z=1LJa?h&+8_WXd)Zi3PMqMMXERLTa(td;Dw}Q{ERQ&|w z;|)RAgT6P)l;w*6{2vEwIwx7br&hT^4;*8Ty<*QJhunkf?O%JGzvd;v{0k0OnyY+0 zk8*OB1-jeJbH|hNbl=oR3wCf2I+pVGV(!gxr>ydJOxF~9muw(gB_3QE`8#YG7K+-s zBV*@XU7IW)lAwNIV=Y0af*#8^&JlZZW&dRC*gW;*1{c}dsra~i$^H93Yw@C`V^DP^ zi&TA(bG+LG23qa}aa$*TMAsM3Q@v=hvlGFHp=)MAKU?Izn|4ZVZb(My+a|PEuE-<3 zd&p((bzKV#5r2rb{oxpT`?qbv-9D=}smCdJBk8J|Zmv7a7&ku7);_`?Mk|$*5FE-u zgpSTms~9_xHRkkPL?UDN`w#Q`tK~I(a1FWY5vlBh0s|dvYDo(1UEG&JyvMQwE!3=Sx~$Y**YQZJ3g_AUHibrq;389at7b;~$dNLmd$qVupi5uyjf& z`%5#T<5&^Q8dK(I&VW@Rs0SU@O<84!>^Dl|m z$~{RXDC=iEfe-XxjZIV`|21o-n6Nu1M30y?$eh`bMPRzZ(2%=kN$&|&gxDp8DWSe= z3}ceKEq)MRNgi-5-E9)6hPM}+9%>)S7iWX23)zZPn_hjJ57ryOa#^`Cn+4{fKQz&W zLO+V~$Y+(f&_(-Yc3vk3(nPKsl<9Cl|G<=GaQ z_FEK1eGJ5m-ldT_cK*&%s0>o6xVI=FKe#+9sccqm1;FR~8Tat%Cf||TcGb#LqiR+5 z75C@U?b!1rpS*!e3hEJO*}CXf78x!|Q&t;>cUuz0x2RAr0Xr6<(moX;s*V}P*fl2a z{^{wSB4YMWOp3Rg-T5?7ROhMh*5gY%{IjIyJOj>y=j)BmKsh zEpHe40KjAYHSfTzM`y_U!XADM+)|qW`xS8t3e}H(^e_r|7rKJ5o<*jS^~*^#;`QKB z2p4Az{HnCMocLVa^ly|X{8Q=c&21}j$7+EXmJ>8*Z^wNG&%M>{(CX(t3qRT&B+r>E zi}3y>Z|3LC$(1e}8a8Ope2#}85e6CLV8_b2N&r6!34cwoIj*}y0EZaL_I1xuZR6&; z7y`O#u z@l0MQMAcfr4x4U=q{#zgyE_y_AnLBVe`&j~GX{hfeV`~q9gvxkwp`hJNRCp5yc6;e z(fn;&eVz!R+oDOIlx&L6Ovy55 z2P<-#e(v=bVF``)`=GIybkntWCZwnBO9sExleb0AAf(Eq(??QhGv{awg`j%nEJN{x z<)W1`JjBk-v~=LPwGD#o{etZ^|AGJBIrH9TznSY0S6HP5Z1MHN29*0o6o6B~v9x%U zx$LeS9>I~L5aoNYwH+znxVQ5j|LOuClwr^rH`d31Ja7QJ$UJ}yhk8}34j2OQ=AwN( zT>Jk0u&V;>iuh^kH(S#_s;<_9)>GZ{i1nQF!(euTZ-Se~h%7WTP;$>;^7J_=lfVPj zED%PrOSa#rKX;eQimh%OO8c6Rn^PXm`i9x5=neY=UVwN-{}PD=b2mL)*p=^(gK-GF z0mnZ=J)OG88Q4YQiHtG)daa6EL1#oeu}Hx8YCqM@D3`Q2!4ZsByF`sIf+YQ$ymglV z6XaXs7ltJd_FzY-L%cXNT>PC4!Tw_Gg*Gat2S{-LigaRdmI0!e!7_WR&B?>13lj=&^=eik}h(r4D~9)db_6Ax`NKQc|$01Y(&X9|jh9($;- z6E+Bk6J+(fg8=|6dx?nES~c)Hhl24JO0BZ8oA{jKtm>Uy_U15zj(ZEvmW{4%nP})o z&Y+n-Cp)2?=1G=waM=6`b>}P_MgmMo1EYS^)PXIjeBs>T#vhrRQN|E6xGmmC*PyJv z10B6&@5bv~e@2CNF#r7I1xfFioN=g1PM=#W!C^J^RT_vI9@LmDeUQ8YJ}e=EnT@0+ zL+AvD0O!zCs>fYs!J>3QjDYT1P%z-*94|MJVpn+}1s`GSODsCy-K%N{o?%8pP(|B* z{?Kjm&w5kEgEWruAo3m%=kjB5!8Y9s8XoMA*zJC-J?@S*OhKgZ1+H4u(O2c+$cT2=Py0VCUv3y&{l43yetKR7^zK5#PgC1a=j8|$ zfJLRd%;8(Xz3}`Em%Wg@GG(3gCuimSb5TQ&Ab_VxYnwT70aPg;Y_fBnl3h`t4siYn zL;LxWF^%kS?x2N#*U5c}bK&~xEKHc5I0hZw>!*@NoBi+90BuhY8wYP7w|OYHeyDQO zs!3qdP;L5tYcHKRyWcnT_8IUqN9P5t0J*wT@5n1>JRk!xQd)Vt<10r7NSY}oZGNCy z@Oj#v>X0)&W7EKP@2>$dC6gbw+^N%G@+ihQbaf4o7G$1*Fa2Di&kLAb>O$dk%scy~ zhsRx&K|b(PNWaE52&7&0Y&3^odgi>UsgQ5DN?Bun_Y%YX*yB@e+T>3jf@-{zy&bH543soFuJARzswbJQe7SE9N-kGq36LfFr0R8S2Mw^{-o zuXhE%kz>%=xl>D*6~%zi5yobH9f4q2BpDj(8x|nD8fGeTz@W(SLCMb!0+E3oqsR1|r$Gbbn;f)Wy*IdJh}W zy3&gJfPSlKfS$oe68Pj_riFBzERVw7=uJKaL8b(Ti2}4)!J5{5#{d3=F%!8XORPCH zWJx+7#?_`vXgHCGxxc&xJ2E&YWY@Hl7d=vBQAZ%_x##T3c>Xe2L)=Yv5kXlYr5Zzs z__>FV-$kcXYn*A}KC;35mpslm1_mJU*-8>caeCl$mc{SS(SddQvXX=fFd}WC4@P!y z^#nP~PV%;wB1og$EPj3eSb`$hFPsN|cbY8698@tKs~(p!g*;19ztcAyID$a;=cj}W z+%R8{Uo-QDdZSdrr2fm!Lk@(A5_y|)3J}`cgKkQ+LY&(p+@F^KD0&)>nA7xpvbro= zxgnmPNB6*hzy{^I;=^l7%WKuOvV9RMDUcR@}ejjv*Q%DI^BW#28=|WGT%%QcNFad=uF_ zI8QkOy0EKX_VnhdC`{B|CqZP2sOT^JU|N3O*~!%uia(uO0DjxfywZkT2X!A{YJTQ! z2~o?hn7D{|!Ui#??DGbC{Og?f>e8o|<8&F0AEcq9<)U@h)j+r45~8ND5vR#>7N%+c*{V4y^3Vx0;r@=7ci5=rw)x> z=AcSVIY(jlk08VP>HO0#0YWCd&@3IOu&Fo7shhUyBFGV=PcCi!MqeOhABf}?&i?c* zjZ|-3n?hb$+$Ae3aIg>vXH+Xyugz`i!j@kh-$v3>f&~uE zkm=I3pt#nIlPfHSoUj7#iq(CR&CAW<{+RtMg z6cx_wKdK#u&x1?2j)vJqffRFBHFR=+A2t_ioj2~-+Wmm^UU1nJ-{MP`@jxNBM7cp& z#HJKT?0%gQR-TWIoa#(*r{pVl4<{E7%lpKT#Djh2NBAa%FT~B?<0yfnGm3QWP;ROB`449Y zHmTbBjQvZPiY~GG&tmR%=5T^X^s| zOZxr1Vdm_vU79?p2|Ltj5_cNbQ*`30FwU`8CK8|XR|SkvM6u!h`*;dj&@RUao~Td# zLE3;*Mj7MJST2nqEXOZG3&k)-sF;Ar#Y;i4&D?Gc%sN(UNXVk6&Y+;tgZn^^56b9~ z<_9+0Y44ulBA%1O&sQC*cFMx+@A|Rf_+wt5TlyVsC_NYrT_tpQxruW>OD>??v0gw0 zMoh$xhhJM4Z?C(xX=~zSE`a)RBL`R{q~p&Y_EEMF(g)Gc>$$f*D_lGsaVDsAY1s27 zZb+X{<3)T@SYiaVVvu}XUIg+<&9F?_#_T-gs@&(`r0>T3igFG~hh#jGg&ZK6^cuLx z0#wrGF68+BIrKUiPC;#A2sc-cmJT+-Npc|g@hQoT?9i5y;+D=Wdcvym?x{c;?m`7r z$x%_Pm)%3^$zwHQL^&L0I|$l_jR!9;uVym(wvm3AzS~!G=%wD^b3IiX^iQgK z+j}OwilF5Jy^S!&U{W68<8-aAnYw$XB+=PS!ZoW~o!h|O*;MzvFlo7yW6f* zGtL2!U@8L~%ys--g;i%kQ4YfuGB^Wn5F^8ECPX_SLr+(Pau17%yTR>JQU#&YH2<}? zA}xbdZ2R8cOKkZl0InQ&u6y%g9G=B;qfB^*HU*T&u&zG)KA;am)3pQiEcilduS5Y)FP z@masJF%Xg{gMeriT|g1o5|fKMnB*RE=#`f|?qf@N@QZEMJ_V1Wi*v_e-whUf2KGp# z&Y_X)FGgK?FLM?e3qIVYDYlXLsr1B>H%!`=wh;Wt?4VHxMuW>F$_|u5_-_=n!)rzg ze+%s@ATU`cGw##dQSE%CBXb%IAf^k=f1dsK*9$|J8^KOjfJni2{16zLbE`Z&MqqM* zC5w2=@2jna!@vqzWxp&EQh1goTc(=r05%TlBO zME_W!ZL|SMNsFu;o0{HL1!@ELmSq<Ypv5aY6S%t zGjQmM(U>T>%KXau`VK)>%&*A%YS4(2XPnaIIZrC${H9BNiEGNVkE%YdL8w5k<$$2Q za@anHuw@|-h0?H_=IRbJaNMEj&&YkZi~Q6 zUM|f6Jdy92Nqlt7%a|f4`#d0|pqpviC$_+3nu6@l2pPs7qvYKCV~ z$dVOjS`1S`eF=nq&@BJhP$xD&|M%VeCte-^@Rv*fPc}#dr3x zfxnVs0kImeq9V#EI1Z5}WeKDDc4z+dwbwA>wN$a3^8Y4FF+vkaA6yQiYtsHA3s0~- zw?w^4{eCCWcq6wkEIGBCnuwBUX)g|=ddD0gnl7ueHlR$EOgyP6t^PPWs*tNm zk|`X6O;TpQQqL}Y?`zMMedlUKwN+36XG=&An1q4>r%PIk`uH|5tJl*aKB;;^cn33z2n1+4l8WTjNH^Dl%}rtp29f3O4X;rbDDxsvFw@wrYof7b0dn=SFF2V1{$Cz%CLYrB^NLWz_KE#h3%g zqvBY(cVdj*=t5n=eEJq#2t8n<7ykymt4wt(ZEwJ(=xiLp`KaBX^^LhM1?7{)zE$Yu$ z)ykqeIYm=Z0l1ceY43qR43~lG?;ivBsr(`Og4!}rC8Yo}T=qtZq!Xr%sTC|~F(XvS zH2P2o$0g&cD7~Sv$*R0Z6WKKJ*Pcgl|9bSPKv*_ugf zlz8<82cP*Bn7|3Lla+LNx!%tOwcd?d30ECmKGH@nAf*rOjtu1NgZZY^2%=>An22sX z4e1{R-&@o3d;b7CEX^TN)}{eV8`PLCszSqfDumnkII;MaZvCDxaa|puf73})%PEwr zUtDNtD0QI(`J>zVkgeoGUXRn>m|w+Yl~E9gMg{fz_fnv+s4~Vi@d+fNAWaYft*v-O zpKZQ1aLs930 z7%-Wh?K-2RxR>(NNp=1h`o${!ayQ)vk(HZQP^K{#M;#)}Y!O-h1zmEBskZ6u`F=*7 z5lXU{CYVW2vMI?8^tR!Oo#6MdQ03lxiJS9PC=f{F)J92(L`-CRR zwRo^x8)Qh|1y3dz3F#-P2sd3Vh0{OYfr$7fvuSs|6oroSj*tmNg?TsQ3-4wQo7DrU zoLLTnC2E1iLNPv5g}rm-)1x$W@rvhqbM1yQtIY_3Ml)#ga$Ceu;O*BJl1+x@D#&qv z)(~9FSw-LA@*n``5bm{M`o^mj6oPRaNgfUEDgMnH>#ai*+t>|>`JJTHK9v*SSW!HH%W@v7dNx+j#F!PiYsn#Z+(P3cfi;VLv` z{6xQXUW4|mAv{?$^Rs_mgZ!SapFXgAJEt{J4fD5=d1;Z;;L#D%oFnkfN=8xTXjIK_ z6WqPZ{b&l^J*)p#3qtH)ju}vKioGSQN((mD;*S&QuO6byISKr+t7UDDBiN4D%h(Jw zB2U#(K4ykFwKcG^DCypdzSyWJFi8NxPpFwK0~SQH&~hho;44b+FJd)c5Ul6re@k&C zMiMq>5SFZg^@m2Eh zyF>xPcthDnQ2azb=~ELb;_M3+-+5R&s8$1efN)MxOJ;Z>>j}Oc_wV(DSPP|9dN?j} zn!O5`x*&Anq+GB10HIF3IvBSYEJDm`3AS_~&in#=g(y}}R#%6_JIgcsR5i<7_m^#V zoy?XzZ^@>4Os;}Zg}&?T&SlMP2IE4hB&NIRQ+G0@_SYiSfe354$KsDRLqfwh#Tq#q zlv~lycudBS1fJ^($S9##AjU)0L=w$G88cSWf5smzwjnu|5BgjUk72Iav@CB$^Tnn9%#bzxvxk%t;glu| z_iDA~8q!OBa0qM6fLwxcC8rjo(4@mI7j|cL(3H7aWvw}AY7%p>KZuFBOyGpbHY)d- za#xlutnZ0wDyj3Z3$ya0XWc%2p6}C(sIvwerZ`D89)tQBjiNSM+x3v>#;vKr;!F4J zEKLEchMjSXnv(%{ZP^V+)`-nGr=&W69Q?Ou{OVEb!G#&m3Sbh0NioNoQn2lxvGu7X z2j!QwWXvLN`Ss=AVCiyH-qZRY=cYzfVG2m9ndRo@(D2MbOc`bMTC9$F!`m9JP&aMg zk8%6;)ZK907jCCN?mo;eW{;^{*$I}QziLd(aMs;d4RbQ*Fn-$wZpVObFhwqz7OfW- z*?{UUQY@joiMhhf|I&3|RVM>$>WoVUCX!1IC@kRZ7lP$z8T5vWhHo}2L2v6cunOJe z68zXVv-dn7Dsn)w z5r*-htUz)6yw)DxWb|AL6bzI90FissI0Qy_QtAw(i%`k21g7qHj~>r-o2jtPQlve+ zhnEZc9ZQ~Hxp5r zSQMI;i&d;^VA5-oS=F>~yKOfScqTX9Me z@eN7wx5cDn=!lcQJZo;orhp>{zdyZ+ZFR*qV%A{5NPMxU3bqJ}B$>oIMyqDXn^?a& z8)!khaCJajH!_0xwN8eJ;Qe`ja08zFdAV;>j1Tdb@In7muSJ1iy_Aox1OE(7 z5dSOl%8$fN7tsHTYkN0kwbhc|#+sMTV7e+R$5?{h^{Ij5CLT(h3yZH_Yr@s0E|s#5 z;CHGyI;lU7q997wt~y1x^Z1G%kIslebnq7!JdpvtK9ED&OOruwHtQC(b1I9nsqsJd zsLw^doRv3hED)HT*QTvg^_6@gWaNVC5X+cAEa5hYLVp(JIBM-sd9AZ_sCB7GAF1n_ zG6S0I{(?|p(weqybte*24|3)24Ura8NOpKNg*4}SxUpsSi}sx4++hYAl0S#d;O5w0 zymgq96MoO}M|8+B@6~snOyXz0#8g_$sd2+rjuViH4-kt|RIXHCzuljVfN9M6#riZTLkWk8E+q4c zZw{$mYyF_mKez5dPaC|RTS_+c{xOzfqXQf)kX~!f?7kV`xC!feSc{O-k97~k_k0~p z5z6YAqkgN{v1?T!)DF&nf|V7E$D+s;5W>tC>~elnixLx<^UCQ7P_a!L8ZYx z&md!ou0WT4V4q|hJ!YHnhHctDLB2C>w&0uzh0Lka%D-~tJ)F8)j6~cS*;qxJ&ip~{ zG)9k6&L$>lJ}B8yrDUopFf&4^9y;~?nOgL>ZWxtYJ4=EOC)}lFweUCT=l#hR0?wu_ zO#dd>%g4tIRaoh@RrapFR7luwd6%$!i0(1_swds!OPa}&zPjSYJ`*zqvm&3@Xx+ey zCcHFWS%33HV(z{Qb<*fPQ|mVsgMxMdP`GjQVj^OK_)ilwOn$UXt70kA`OuqXcd7lw z6jsIv$V-P-1vpLzfekBFReH?iT71FuyGN~U zhq`h80X*$Ax%dD#c2${W-=GnSlU5NOuvGP3ENmG=v;n?30vhPR!;S7cFXph7}}>C30LoSwn(;+0I48OoZ+V)IxC; z4=wW}IkKm9i(Dq_mB^7ZDVMRJO{0;5(gAkZMP!+AG*SmPq~6R>!cb*=be%rN0_2|l zV6wiMsLUW+yqKz5t{FdDQm0B2TzdvC{~K`70vUwAr!FQEN@lA)s`4N-w%70nf%FAV zQm-+jgr<;_coHr|@fXcz0`3Ibyb~8Ia|KF8%Q{uLWP^eVo>*K!Y}T#i_aV1&WEmVrO;(n`tk z^)D(K{CW^-7VMmNBoIWkVcR8VrwAe_p@M?1Z97VC)yFN|ya)nDRxL8VB8HfgFZ=yl zMl!sWp2U1)0zWW!?fKvYqA(2s>kso+2&Zd58tVK!|CF#;d&~e&yl8jaI`oxUV;XG0 zl5FgfrdS=71v?lf6mSo&0htjK6IY(gZ`tz2PbcrA{yQMh3S zd>zoiq3(V!@VVF?dM>+pOK4mgE_ryZA(QjDv6L$sYwW)P>L3U9uR~?5kIU?mln`k_ z6tijYEY+(h$WPE{6$B%IPKB@5a|hMkM`B`o_zX6A(9J)f`JKFHoYD812=#(U1PY~W zZM7{W)aeJIEW;C)8krV51uw~~i;T}|*2z|$ChZ+xg+8ixNTvzi^3P2_U#Jw%Pn{oO zY_J#3oIT#z3e;0Q&6;vDlr;g>WiYptL5hngVwrdXCJf>6i9gUwFfo4(1zfoWmw`Qz z&UKYay3#chAj8bH_+-03VK>AM+y`+-q~{l*SLU+`3*18C!63=NI1mLA;Eawi=va@K zcm-CrGu>M0fUHQsu*a`cjKcyu%Lmz?L!WzgV;u(7m!diSo2dBs5Vh1_l~;*NX(~lI z+pabN!WXfQB~~_H_910CE8&?Rr6D6N>O+5=v!c4PiGsMd977tG5>2dF->5A)^HAut zEuqo8KFPayuUdtBW4}}_1IgsLeYuludE8%3;T*)`oNaN)s#Ey8e(POx5 zCsHS~b-G><6g+OENbYgp%7~xS?_Ps%BX_j>iFJ{_1(P#w-)+EUQ#d}N`DUAd(glZp zf8U#irWbG#I!!qCF4RfcFLXJoWS%UwXzHS{9-i2%lWV{}9yhP(rT7_RR!l@tqGc)1 zt(z9bzdE8rTI)SyR6R41CaEAHM?tX6XoYukD$;TU;w=UwY&VEQSv>!t!!&}SkS=oM z4|iorMyg;i6mN`_Y6+bq7N$RuNUyZ+FIv+GKg3#41=nmdPck*sk;ngZlSt{89brMg z9KPPLJlEEou1 zva&sgsozvcOR-19c$vy_1TD8DI(*b}77nJHMtz-9uau@uw&hSQtk`hF8A3&gz%P~Y zBa$w5nj;EajZK?el`s{AmoT>pm?=^_f&AjO!Wx<(rla}^*QSYHQ>gzoExiPpJcCsp~LfbjI$$<^pg z#a|+iH}&HTy^Li$=7!n&OjXpqj$74YmupB1G3B*5j4?AI+lqcGga#vA=Wknso(n~p zPD6M*tGhsJ_IjEJi43rI`3ThF$~oJPHy}v{^EQzz<>`W80BRO^L+GS0+xMmiXT?Oz zhZ`|5|Io(p(!G;dHC{9I*goM%h&f_lx#xaz=qAsQQoEVBU%uWh9#7AMOK)K>b~E(y z%bjzPK_{{~f%HrrNZ|?_{i~murf-CE3R)=(E}IR1WCZzpxB79OdDsA}9(~qpZNuQl zcfcQC+-Yynqfj`cvhVsuf}oUfuw@iPyNss9xv(M75*YRy!-#e}*@RjBiF}|TXgZ4a zNc(!eW$vmhaG}DJ`{5)DOi>bfR+Ff&GP)UKF;xvA$!T(l#;i}|7; z-)uh|L++&>=o$Og8NfLjsERT!vU8RJ%CNEiQ+e-vtH|rTOqd0Oz_CV&aIN*`AqT_a zqQf^=zZ&kbrzUpchmZy019rD%z`O26&v;(AEC$K@mmVW#i*plsNA@t|+(I<#h_r1c z@i@4)QIX~(PYdK_VVY=Zn-fgO7zM#xdy=*zJsGK*$vHKQTbc4qqChW+F7q|RJ=ir& z>0aZ{ft1Mm1NJpKk3%i>>9S8*%W%<3Pt&t+=&hkQ&_}4agkhe8wZ&_BNxWjnb@-(x zJU?r@0UaX@eM}8xbcFML4!VEJ#4e~UO<0>pp(I1h9|Q8P57_A{$A-}({JlDa1uUEH zy@oiJqEC-mnQV=nZ?;06gGf-GpJ(SAEjuvwNHyvBgJv~&RvO>Rg|c~qe>C5D*zzKd z0oPlLR!!^HvpduFeNTUlH)AO*M7l+fB!|qr$jWOhGO;l3@SSs|Ujgp3s{I-WrZIOg z+sKRjjAXg^>fB*TIBGMy1BEakMp3!JqydN3xr1zdcb-_@G73n=XJNC`O*7%HJ^r0U z<=BEP8{zWd7HmQO^ljpTWi)u9f;;oOd%% z+45XDX{O<%=C9=wK#7jbO;N0}EzO;jc9X0hvwMz&=j)hFN0SW8vQ`yg`@g&N<&(2% z#yt8ERv-wb-&Bf>eRMoN*p_oO^A2ui+|kqdPP8ggE*^FO7-_%2|D9Xf4`~UYI)Jz- zB6|~p->GQt=X~p4gFgmVTjLaa$|zP*TEy2W0!1sDrT=9duO9$k-&am8R+Z|uF@oh_ zgD=1_@pms3joJ%bcXPcMHVU;k^G8Eqcjo?F$Ic`RVWQE>pFmh)!J14v_FaEv9c@zcC_GaT?FK7ApKo4jxj~En>gXq}0iFEjk z*m__K6pFrly34JGyb{+;+;8l%mTjlNzRt%_k&@G}r~^xpPRl$jlup%F)Rky#8e$`^+n&5I z!iG>L^)Sh(OqLS5B+Y<0=6{>z!&c+Mim-2!NT{&ie5N;FuuWIm!SG1$32KJ5^E@Yk zyKSW}XUQ1DX4_&6a{TuXCw_4bM3r|aFl)X_Kvd=UAy)_PjGw$x#9xsOa^H^qJ5zpp zDbo}`G_a02I}`@ipN^F88s39*A{?CRE=f1w+?IAOY=uJi=N9PXT=%!{Sf}JAq#@5f zOj3q1B~f`0GNi{Y=LH^^o7XNr7fwV_E+N0A>i*g7W} z=7GLdGrB1PG~$FY(6L0awRuV8yM-K6A7=+IuA{@T zcmqy5xbbihdB78wEbz?Dl(TU%4+>+nZNTHE!qdmH305d#P2k2`A^!fe|KmWcPMZ131}LMzfF<+%|xd&=N%kR1aySzpEf{H-|M5`sqG4hjL&BMrsK=!K@CVehOam7`E3F?{r8TiH{-1@=;IuAN>@u&e z)(xpjWZvmvPFBBaqA+MpSRv&_>fjzX^_TT93*(B*G}4B*dX%ghOcyOgL-R(38u5FF zP>rk9wE7p#ux5>OPFO0lbN<9qTZyMr(e`hatILUvxRP)VKC$daNLNxek1{WIHp+I;_<*d8^7Hzdwt;^%uFtMX)texEXyr^7UPo$=s-fHw6p}iXIY5Rz7fgG z^XKl`9_x9I!+c|2^T)?p&FiZH<5l|wF!I?AK?0p?sygD7RS=;No&c71mRTyFDNO)( z-7|(aSA;o$Ynks$^-N=duD;!n8A$x$lyz_^q6#Shhu<+@?0nUgo2F!f;B%DAXwhbb15Are6H9%xlD= z)gVPT8!73XR9IYMTh{0K9y7)p3yfAjxS4`T2PA=Ky=Iq=Q zXQ89+-S(u+iWde1+#@-i?BQDb#6?fXjfe|xQ ztNt8m2TN)s{DCDZe$<2BkGXTm4O{M9M9DvL8!Zp)i6pMaREc)guFr1uqgccmh z1zo^{#q_xaUsg`exV{xzyp#J=D$?l=F18$te1qb}SV=aBrv#5%!RZJzDx;YVt;@x> zm!q-H?h_8Yv^75M9uWdko3$*Ly|a06C05ucz*M>7uOzVJB>c@ZO>CR$n!36anNH2| zt^nHxGsE#bI0N#+%rSbB&x#5_hO<8vKOxxoMpcAI;aO;q2MDWaa(2v#?=pjxYWXWH zcBjJJ34Yw6dB%Tn8)-xVydj+hZ*(?V3Pq)oC__*G$Y>X z_!C0Rn^i)xqPW;Ezkh@})u>%>JEp5$Uq0Q*@{?gO0mRdEWF@)oCtnCCp9EE;Q+y-~e#z=8Rh;g9N^bMKULA=1(%lFUW z!ywXt@qhrx)++FNb^cbJ%n%+EDhMV{PIv~Xxe7!AE5`3*;=I1d02A!<&rmfJ74ia} zE>6te_s7daNMb_OZ!1KwXT+W}f?zVj>j=Ml&)>Tgm8n#bMrJTn*4s54nylYT_TKW@ zE1fW_S{2I_Eh)mM6X68-Cq>o>4BecNa?XsDKT^afT^DX{W04Rv$UNo5M!Ef-hhN8q z@sNzK>P3N?k7%t1k{o@qt->SKo#r%I(S5odp>~sGjQ;3>#{!fZ zbA+llSr|r!!sE%tYF564eH-@cEA8tG3ILOfK|RU*IEotLqA|}a5+)I8+w5_~GZ9AI zv}_6>D=AE&`b2T^wKOizf@H@3;TLy+meHIdkTT)%FUi}+B5=pX2XT~^vl~wz_XF9* zVds4(6oFC6`MK3Lwkl&0d^L&=nW-jA1Bw)H5aWXpwLAta1F5uJ0rBpzXQ4V!5CJz$ z8a#t-@2okP$xO>2326o!W1}%uQc!-iNM)koW=?<>iCiMubE~Ui=JXo122F}OWpH+b zevQpD?NF^{bmajAxtcDjQSe7kT;DkMT$pZ;|5TH8D&W9)15C8zCmI)8^wRjUHIe_R z7Pb+FpQA*PVl|GmVh%i~6U6Ls@Ee2YSpr!u2F6}cU}ykNvC!50 z3aOF8TK)@l3ALRQB^7U(Dw`{M7a8uQEccHrkmGvO zKm2u6t?~J=ULe)Nx!n`JI?RvV;eI+j-NLynEFu=!YmNoju3li_&X%5f8U=u~_Rz%< zx2ke8+L!yO1u z_t4d3bkP~hNC$PdurkivqdlSaxS0;gi`SFH>Qi55?^XuXg`Z=?tk)x-nu{V3h5@Gp z!N^m!@xgoe49h`cr!#8;)tRbQw2}`oo=w-?S&FHdarvZJydbnG$@ZXH9;v#L+<1&Q z2RN=fizVQ;zyr&)mb7mwMgWD`$?EZRyXZ@{2`5-9f3z}p2nGn2*xv@fHwgs{B3PAA zkQ>Lze@|1LN?mJY3L&weq2$+$veE~Wk&G=)_q(K(^1Y*Ikj%{?;B zL;f6J9rDMm*t9ht6a1;6wdH>Gw`zt#5$V#F^lSO5q7l{&lRYL-&nAEMa;R1*mm&Wu z(70C66$E*o8?_o1{<=5^#>SX1zs10T3JbMVgfs{$YCUsoUc&L(hq#FFvg zYo~kt1J|Z3mk7LM93ScC(ObX)ltcR7Mv;%1W8n~Cn^heGAP~VOScMJqr>kTggxvv&2ctxW%mn+I1@+-wlJ}c2PPJM^v~}0-DzjW z_=N-)lo`&|zWXRaehX2(b#)gjD@PoB7I>l{(_2?=JL+~K<5i`W)kX!2JIA)65Y@9G z5>v~~UfYTjxctbUj2WslD>Nx8x6(%>j2uFtop0JMUd5H5j+IQcorhs&P2z;FNN&wn zp>>8y0v#vZ);E^Mi~jr(0PG zFkgB$BootkN6B~7_(Jh)9hWRi{qWCQ=Zbm|%#uK%@bwe+4Goq~qttPNLZido9 zYIxtoH~_xji+NNvB>F{ulcYOlmpD*h+(Pg%$upUi1|!$6pRjNjxHedR30Q^*u`<-` zY7PC7#B-t*E{<7Alu&FB{luMGf}IX=VRHFeH-;koXqAO-YG?$Fa|p+adHvJ(UQd&s z^kBVyA9SgCO_2Xqsl?Si3MM*H^nizeYYyK92=Au32&EKFo(d-@#|#0v;f3L{fQPD? z`c;q2rG@l|?0W-$nAA(;>SQ%4j;mNJCV7SCt*48J*L6@mb3n;$S}=4%r_$PrH;Ge8 zzPU?Nc?I+U)q(mQT`i{u(#jQ}qyMHWGhmWAY@^g%EO8Vc>hi|ab01CuKkk zTkAslZrl(ii{`qmo&J7IbCqAT=QsAzgQgXX)$0U9YR7lQP<$soL!8tHb!TuERFx-q zsFH9Z_g=ca;3xGU1aVrfV2*y+QW5D+`kYhgp(|7$js0MX4VN`ySXlb2f%C3L*JOAe zriK;wmdsP-_LtJ)6%dBu5~F!711)3=`+75IpmG4habkQ2)Luq{dDN18P+3pQsyCQ) zxh1J3r2t<7Y=x4h!;#G~;+L%|dC`W30eRC{q(PmSy}rPH3x zC}gf7-6&c~qyGMx;3rz(iYe|8pCE2s$K;)!shCC$>lWa5!(STM*lPEsO;uP`Q9u}b z72^06U*H%EfkLM*x$!0+_E~k3j5yQ9Z852Y|C78LW+i8)k&7LG0?AGW2qGvSHy)JtGv1$%VQYL1Z=4mx-2IYlD_=A$JV;w7)?&-anru_L(l? zZEif|ZI$dPZ&lm;h;P$-o}u%~vLn0*TPkALecyXKx345r?xL1u4D)C=)WWmyJcf1q4Ss2M}u_(Wkc;%pgEl)*t?QX&3|hJ>(DEC5zDU zk~w~oF{){^`>8a$s>EXK(6n04F;OEp!XWcZsb3xiYD>GXe6rTh?8uE^rRFMc{@AfJ ze#L(05H1SK&uqO6>mX<53?!kOX!h8esR7&lzNl)(gTlKbvkyveU~k&PhIxNOn$EQwI4UXF(itO>AOpBAc6I;2s@6>RnYb z(24B?%|yW9WJUN@_#TcXgYO|lj%Jv_TL6Kg(LRpO&Sy9B)i9rAMWe}zR(GV?@Zl3% zyX`AZI>G=UB5Dsi_RL>xldvv68FQzM!HweXLiAM{(1aMLOJ7UN^axvt@Tdzog~63^ z1N`QsqN4D#@YeP6SA|Uz_{8@J#`64!Rnp>ZAoU}D5X@3PDL%}QiB9AOV?w0tjs-3) zOp0MoB2!z~2}|}zSAafx?efvVk_vY;KWY^s^qG{&6*!N`Q z;ZhyCZus7gn^tQlqLZ(tzYtM<<`duxC~B244(lO;lm?5hJu6BUmv&bTW^7Ym2C?c7 zI6Rfwz+9@&&*KF* zTP>~}LZ)5o=zDngW=>S>v+X6K?%j%KR_gl6iCN5RMEZq}9y!4(MSP#jA1lS}FbTRx z8&BJW;gU-D-b1&1_N_G53-@8xq)%#+jnLr~#iWQx_QO8RtE)`7j`31B)re6-hxPYW z%3-oULMUAD&o?NDRZs^_I#)wp(H+C4A3spg6J$wXo2k)vzlB&4z!5}%i(ST`P{LZ) zEt2ZS!P=sXoSp00UJ^#VnS*oh5!V-_v(&CN_VXaK@L%$VbT3*uq50pP7_?8Za1X$I zKp{T+V1fpi8g8kle)G&{`DVdB%fv5KsenElguU4~1`14$gn=&yxTtH42raAkhD=Hd z+0;<>O7ndoNx$D8>~DKmxH`5N4^B^YlZbg64Kr31JzVIv>i){Z)*wFY$lzGiE6u(j zbR}m}I8M^}qg(d$boWtEn=~lc=lgFDgn<+6PS#yw_N|0YblImBbP6 zD@(l4ki=5QkreSnHrsL=Sk6w%cVZSZy8Lx&lVK1GN|DEUm#VENO}MwW8}`Vkn;<1= z0b)N7Hl*#%!M<6NT;W-vYp}YNi)UO>t2^YzREd6JX{J<#ro(AyZ<|yR8EuAUUJ3?y zS%WXd%O@}za9IH*M4_z6Pb1<*ar@pUU3^drqPK`e*)&_`kn8&HoZ;;Bnbf^~kuEX_0 zq}`*;NiQ-fh_Yq+wSD~uZ7*ceNWCb~Wx}x_f7J6=x63U8jhgd}sy^4dL8z%wC_j9o zMB4jbjMGZ5*>DU);A{p49M52Co$YZ*8922YadF`-M^I$SSme$k@8(vcUGS0#9+Fq? zu!jN`IOagAa-wjbbX77c5=$^*Ef5);4PCH2L3~SzAY}@uxp&J2%ayN^+b?V=SDq<_ z>OV)Fulhobxj-{v5$)+U@dTe&c*)Sic95Q?pnS-pClLB9u)fU@=oZj2dm^{>U}1K9 zqAw5%kJtPb%J3vX*CToQf}Y~qrg2AX$!6JJa)BcmtnT4GBd8!hA9LTCwR?Edc!Du? zeL7XLLV+BPiXO7!Y!f(mc^pW{PG+;U?ZZbTjLS6JmIBS>yfYIW)X%sH)#2*?N*wsy zQ8wg+r3{|{a&rrGH+JLNizaoiZZOQ--9vuX#TI(owSVJqm49=!Uwr_RfJS&aB?=$8 z@>_Zq0!al4jIc4 z&5iYsJ9d{2(nt@FOLx7X>^X)pjA}!gg-cbz4y>VZ6U-QO5KADcNdd7birCV*(jvnw z?>qV15Q+d{Z7rH%y{FWwONxK(dgtUZaN1feJTC6JcDy5YM@BAUF3f<)Jnu3$$^zs~ zZV<=Xqqm^<7q)vcR%D^Ala*R@{}b^N7v5=|@ATp?7PUT3|E@RjM`OXHDsnfq7x)0o zdD!}l$KV9FBiHWq*Pgx>{uf6<;bNvKyh*E_6z_;LG%-GO4>l3n5x~jz?bM=aj>XLu zYrIfXJY;G?_FRBjs{k6;)B+4)srjfWet>y~Yc>5~z*C?*<$y0jf5SF$RkGxFd9fTr z{it7kA52wQ&$FLUyNVOwJA4f%ofC&1?T%_Cf(mP6hySc-R(?HOL!#VfR8|=V(#y*9 zjNHeY^NbNC+AeyAyaY9IV4Ns73Uhtg7XdkO@!r@l_49{^%Gxwd4r~_~Nq4xcKzb6#M^wggr?j0-SH;)lG z(q?m9G!AO~*jtzYf3DMm?B4xn9nMQ_DeZKGE;I;cDsGpMHt39%8AjadrL*uonS#iq z6s+Q^WvCK^euvQmcFR6uFIf$G7mhv~#CUVPIbV6L=U7Yx9OfJgjj4e7j&$s`p*JiT z+X(dxvm9`Kup>?pBeWRT5uFwihGSSpENPF5*2ug6l;+|KJ9yuMe zt@PpNq$K&0Sk$^}7gIeQ8FLG&zDjUk)C@v5@-w0`n~&l^$SwCCq8dTR|)KyMS9f zQ=%W1Pz*rp{?1_j7mO2QUon@So4yyl?%XBXmk79qo1+SiXE#~+y8{l;qZ6;1<;va6 zGr;|iv1iKsUL>1E@v~%ISd3m6pOJzt1}O+fGd1u9<$=y|%_UEH=66UURGQ83U>dqu zK`i!SbeTv}MBgG#yj#{ob_k|Ksv`#YVJyjQOb%de@)ve?%|JAyIDB0iCiv>O8D$K| zxozmdR=WLF>NVwjDsqiX%B8jxAlS2>I^lBV4`+q5989=bt+KuJ?W(z-Rz6B!u%zO? z5@g@@p8c!{X%Rs)3+u~3F4PES5d0nkn2m(N@glx0T)~v`?nv--aa9y>^3hs0)a=wf z09MXd7>H|BR8FHKCuFosf1hL`$O;s9a>)h6@UBPq;%_J|X1*Q>^~tA=vF_HMwLz^T z18ise-NKte%~xliph!a*sfo?qvHf1--vkN8c$qBy8osg3wCJ&148%!WgTaOiG^|ul4SHs$b=-uBzH4uV-bVvj?lk^ajqWmz>ue ze6OOSP&x&brAi9N>ZU##EP-J(&rvOKgC;Wsyg8nx}m=VkEq*o{YC!br!IF++yC9X`zB0jr_nHB97K(*(1`j%E4vjCRVctzUGRHy~^C-Hz& zpA<4F99HlAxx4e*8civU>47%!C=}B&j}qE7=6wjdRb9Xji>U}42@Li4F8>3xeK=$n zNS?;nKr;f4;TPW3k1M+=);XLQ$VklJMv>WWD&d43+0DW>!)!QrI+`St7(^)S8m{tl zP24Slld01rpNI&{O<_XVU;0o2#lH(^g0>PBPOLRjthYU-^$k2=yXmYU>{r}OAOeK=`I>4hLaTPDGEFlL`fQiRr-P$3NuEHJ*<#4Sk-b9((8*XX1bl6ke%{II zAX7K4M5 zt;Loq&L=a-)BgJ7*-s5B?pVFLuUS4gD`B*fR3l&VHVN!fc#UNj*vI}lziRz(rT^!X zBl~~R9huSrh;?h_T&wMMpb(C#LUqjwb&g%@P-agHY!K{sAuke|qsw z)crcZj2?Ok@keaTC(CzxX2vzYkq#;lW2h@W+ota@hYE2~v|xewVf>GsEO9y>&u zG%uiJuR+dCrKo0NAvF5DPPt9UW|pdV9O>_h-R4DDT4z95>ixOg7`)+Xf7K#nHP&{a zuu?3uazV~xsKMC^BDssSeL1mhbIL+k)rOH6kZS{UdfqvJ!hO7=tQ~6Dl(dj6u?fb) z*P0K&Junjpen2pt2%pzOSnaHi2WfJc+)pIyKF@NIJq4tezvw|rYB=pIrVGU;@e37N zX0jVl{$B8+%OdPDhGU3XzTC=q{f3IUtn_=?a0CzJ?Hc)xj$=BsoebO^9W3X+k$r~=qr7~na?ZQ-f`fC5G!nvdgvshjf3h2Jyjz?dthSjUuF20 z^y>s801Qtg~K&(`uAl26hT`KlFzU77W zX@X_j*7a(mfrX?-F+l~%j~yscoDgI#n?I)uq}HtB*t@48E--M5Qv3qD_m3Y~4~!{Z z9L+*O2-ApvPucCp98w^jOi#fk(YngEZ?d*6T}2rDCKUE0NvZHjM!K|H>YdqB4D&MZ zp15!(A!V*&Ft;a+o~Q%GVvT7k{!DZTk8D` z(|=`65Kx9sTrng7`g43p07e%%=6avk;=%t_vf-~m`^S;Cn4PV&iLJA~iif?4lkPua zTJh_D8nmb~lihvZD*Ksu;r>IN_Bku`9ZjsAm>B=D{ku@Lf+vdmQ}p=R^5^XOx25|t z!@FBEx!E~d8QIxc{kK*F1e4P~vCDwYPXpY4lYVvy=AYt!HT>@u|Mel{dkz%|#Q=b2 zNKgRwfB2|;cIJN{!pYpk#Mz1IpSyn+vG{ve{$BUuFQan#f4cm=3dY|J{?17M%b-W) zp9X&s)Bi60JB9i$X{!3aq}QLggI literal 0 HcmV?d00001 diff --git a/unittests/tools/test_deepfence_threatmapper_parser.py b/unittests/tools/test_deepfence_threatmapper_parser.py index 8561920f61a..47cd67a4e0d 100644 --- a/unittests/tools/test_deepfence_threatmapper_parser.py +++ b/unittests/tools/test_deepfence_threatmapper_parser.py @@ -13,6 +13,14 @@ def test_parse_file_compliance_report(self): self.assertEqual(findings[0].title, "Threatmapper_Compliance_Report-gdpr_3.6") self.assertEqual(findings[0].severity, "Info") + def test_parse_file_compliance_report_newformat(self): + with (get_unit_tests_scans_path("deepfence_threatmapper") / "compliance_report_newformat.xlsx").open("rb") as testfile: + parser = DeepfenceThreatmapperParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(66, len(findings)) + self.assertEqual(findings[0].title, "Threatmapper_Compliance_Report-gdpr_3.4") + self.assertEqual(findings[0].severity, "Info") + def test_parse_file_malware_report(self): with (get_unit_tests_scans_path("deepfence_threatmapper") / "malware_report.xlsx").open("rb") as testfile: parser = DeepfenceThreatmapperParser() @@ -22,6 +30,14 @@ def test_parse_file_malware_report(self): self.assertEqual(findings[0].severity, "Low") self.assertEqual(findings[0].file_path, "/tmp/Deepfence/YaraHunter/df_db09257b02e615049e0aecc05be2dc2401735e67db4ab74225df777c62c39753/usr/sbin/mkfs.cramfs") + def test_parse_file_malware_report_newformat(self): + with (get_unit_tests_scans_path("deepfence_threatmapper") / "malware_report_newformat.xlsx").open("rb") as testfile: + parser = DeepfenceThreatmapperParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(66, len(findings)) + self.assertEqual(findings[0].title, "spyeye") + self.assertEqual(findings[0].severity, "High") + def test_parse_file_secret_report(self): with (get_unit_tests_scans_path("deepfence_threatmapper") / "secret_report.xlsx").open("rb") as testfile: parser = DeepfenceThreatmapperParser() @@ -31,6 +47,14 @@ def test_parse_file_secret_report(self): self.assertEqual(findings[0].severity, "High") self.assertEqual(findings[0].file_path, "usr/share/doc/curl-8.3.0/TheArtOfHttpScripting.md") + def test_parse_file_secret_report_newformat(self): + with (get_unit_tests_scans_path("deepfence_threatmapper") / "secret_report_newformat.xlsx").open("rb") as testfile: + parser = DeepfenceThreatmapperParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(15, len(findings)) + self.assertEqual(findings[0].title, "index-username_and_password_in_uri in /var/lib/host-containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs/usr/lib64/python2.7/urllib2.py") + self.assertEqual(findings[0].severity, "High") + def test_parse_file_vulnerability_report(self): with (get_unit_tests_scans_path("deepfence_threatmapper") / "vulnerability_report.xlsx").open("rb") as testfile: parser = DeepfenceThreatmapperParser() @@ -40,3 +64,12 @@ def test_parse_file_vulnerability_report(self): self.assertEqual(findings[0].severity, "Low") self.assertEqual(findings[0].mitigation, "2.5-10.amzn2.0.1") self.assertEqual(findings[0].cve, "CVE-2021-36084") + + def test_parse_file_vulnerability_report_newformat(self): + with (get_unit_tests_scans_path("deepfence_threatmapper") / "vulnerability_report_newformat.xlsx").open("rb") as testfile: + parser = DeepfenceThreatmapperParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(254, len(findings)) + self.assertEqual(findings[0].title, "Threatmapper_Vuln_Report-CVE-2005-2541") + self.assertEqual(findings[0].severity, "Critical") + self.assertEqual(findings[0].cve, "CVE-2005-2541") From 573e263b85c3472d2de98d3a64309869acb5d08a Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Thu, 13 Nov 2025 04:47:49 +0100 Subject: [PATCH 10/25] :tada: Add VA vulnid (#13675) --- dojo/settings/settings.dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index cbf43596ce1..54c9af4f23b 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1943,6 +1943,7 @@ def saml2_attrib_map_format(din): "TS-": """https://tailscale.com/security-bulletins#""", # e.g. https://tailscale.com/security-bulletins or https://tailscale.com/security-bulletins#ts-2022-001-1243 "TYPO3-": "https://typo3.org/security/advisory/", # e.g. https://typo3.org/security/advisory/typo3-core-sa-2025-010 "USN-": "https://ubuntu.com/security/notices/", # e.g. https://ubuntu.com/security/notices/USN-6642-1 + "VA-": "https://cvepremium.circl.lu/vuln/", # e.g. https://cvepremium.circl.lu/vuln/va-25-282-01 "VAR-": "https://cvepremium.circl.lu/vuln/", # e.g. https://cvepremium.circl.lu/vuln/var-201801-0152 "VNS": "https://vulners.com/", "WID-SEC-W-": "https://cvepremium.circl.lu/vuln/", # e.g. https://cvepremium.circl.lu/vuln/wid-sec-w-2025-1468 From 038cf1695e149f175171646da63f1723cdd53ce3 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Thu, 13 Nov 2025 04:55:45 +0100 Subject: [PATCH 11/25] :tada: Add Kubeaudit fix_available field (#13684) --- dojo/tools/kubeaudit/parser.py | 4 ++++ unittests/tools/test_kubeaudit_parser.py | 1 + 2 files changed, 5 insertions(+) diff --git a/dojo/tools/kubeaudit/parser.py b/dojo/tools/kubeaudit/parser.py index 26638bd6a2f..af7babaab73 100644 --- a/dojo/tools/kubeaudit/parser.py +++ b/dojo/tools/kubeaudit/parser.py @@ -81,5 +81,9 @@ def get_findings(self, filename, test): static_finding=True, dynamic_finding=False, ) + if msg: + finding.fix_available = True + else: + finding.fix_available = False findings.append(finding) return findings diff --git a/unittests/tools/test_kubeaudit_parser.py b/unittests/tools/test_kubeaudit_parser.py index 38b8e7ee8fd..9e5bdd0928a 100644 --- a/unittests/tools/test_kubeaudit_parser.py +++ b/unittests/tools/test_kubeaudit_parser.py @@ -15,3 +15,4 @@ def test_parse_file_has_no_findings(self): self.assertEqual(findings[5].mitigation, "hostNetwork is set to 'true' in PodSpec. It should be set to 'false'.") self.assertEqual(findings[8].description, "AuditResultName: AllowPrivilegeEscalationNil\nResourceApiVersion: v1\nResourceKind: Pod\nResourceName: storage-provisioner\nlevel: error\nmsg: allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'.\nContainer: storage-provisioner\nResourceNamespace: kube-system\n") self.assertEqual(findings[11].severity, "High") + self.assertEqual(findings[11].fix_available, True) From b097ced6beeb64550adf6032103029a23b5767bc Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:36:15 +0100 Subject: [PATCH 12/25] feat(helm): Relocate docs/schema hints Signed-off-by: kiblik <5609770+kiblik@users.noreply.github.com> --- .github/workflows/test-helm-chart.yml | 17 ++++++++++------- helm/defectdojo/Chart.yaml | 4 +++- helm/defectdojo/README.md | 17 +++++++++++++++++ helm/defectdojo/README.md.gotmpl | 16 ++++++++++++++++ 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-helm-chart.yml b/.github/workflows/test-helm-chart.yml index 5f59843c06c..c66df37e57c 100644 --- a/.github/workflows/test-helm-chart.yml +++ b/.github/workflows/test-helm-chart.yml @@ -123,9 +123,6 @@ jobs: chart-search-root: "helm/defectdojo" git-push: true - # Documentation provided in the README file needs to contain the latest information from `values.yaml` and all other related assets. - # If this step fails, install https://github.com/norwoodj/helm-docs and run locally `helm-docs --chart-search-root helm/defectdojo` before committing your changes. - # The helm-docs documentation will be generated for you. - name: Run helm-docs (check) uses: losisin/helm-docs-github-action@a57fae5676e4c55a228ea654a1bcaec8dd3cf5b5 # v1.6.2 if: ! startsWith(github.head_ref, 'renovate/') || startsWith(github.head_ref, 'dependabot/') @@ -133,6 +130,11 @@ jobs: fail-on-diff: true chart-search-root: "helm/defectdojo" + - name: Failed Information + if: failure() + run: |- + echo "Your HELM chart changed but you haven't adjusted documentation. Check https://github.com/defectdojo/django-DefectDojo/tree/master/helm/defectdojo#helm-docs-update for more information." + generate_schema: name: Update schema runs-on: ubuntu-latest @@ -140,10 +142,6 @@ jobs: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - # The HELM structure supports the existence of a `values.schema.json` file. This file is used to validate all values provided by the user before Helm starts rendering templates. - # The chart needs to have a `values.schema.json` file that is compatible with the default `values.yaml` file. - # If this step fails, install https://github.com/losisin/helm-values-schema-json and run locally `helm schema --use-helm-docs` in `helm/defectdojo` before committing your changes. - # The helm schema will be generated for you. - name: Generate values schema json uses: losisin/helm-values-schema-json-action@660c441a4a507436a294fc55227e1df54aca5407 # v2.3.1 with: @@ -152,6 +150,11 @@ jobs: useHelmDocs: true values: values.yaml + - name: Failed Information + if: failure() + run: |- + echo "Your HELM chart changed but you haven't adjusted schema. Check https://github.com/defectdojo/django-DefectDojo/tree/master/helm/defectdojo#helm-schema-update for more information." + lint_format: name: Lint chart (format) runs-on: ubuntu-latest diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 3e3ef73d073..4229d5281f1 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -34,4 +34,6 @@ dependencies: # description: Critical bug annotations: artifacthub.io/prerelease: "true" - artifacthub.io/changes: "" + artifacthub.io/changes: | + - kind: changed + description: Location of HELM development hints has been changed diff --git a/helm/defectdojo/README.md b/helm/defectdojo/README.md index aa468e6bc61..d69d85c80c4 100644 --- a/helm/defectdojo/README.md +++ b/helm/defectdojo/README.md @@ -493,6 +493,23 @@ kubectl delete serviceAccount defectdojo kubectl delete pvc data-defectdojo-redis-0 data-defectdojo-postgresql-0 ``` +## Development/contribution + +In case you decide to help with the improvement of the HELM chart, keep in mind that values/descriptions might need to be adjusted in multiple places (see below). + +### HELM Docs update + +Documentation provided in the README file needs to contain the latest information from `values.yaml` and all other related assets. +If GitHub Action _Lint Helm chart / Update documentation_ step fails, install https://github.com/norwoodj/helm-docs and run locally `helm-docs --chart-search-root helm/deeefectdojo` before committing your changes. +The helm-docs documentation will be generated for you. + +### HELM Schema update + +The HELM structure supports the existence of a `values.schema.json` file. This file is used to validate all values provided by the user before Helm starts rendering templates. +The chart needs to have a `values.schema.json` file that is compatible with the default `values.yaml` file. +If GitHub Action _Lint Helm chart / Update schema_ step fails, install https://github.com/losisin/helm-values-schema-json and run locally `helm schema --use-helm-docs` in `helm/defectdojo` before committing your changes. +The HELM schema will be generated for you. + # General information about chart values ![Version: 1.8.2-dev](https://img.shields.io/badge/Version-1.8.2--dev-informational?style=flat-square) ![AppVersion: 2.53.0-dev](https://img.shields.io/badge/AppVersion-2.53.0--dev-informational?style=flat-square) diff --git a/helm/defectdojo/README.md.gotmpl b/helm/defectdojo/README.md.gotmpl index e4ab067a647..2edff657296 100644 --- a/helm/defectdojo/README.md.gotmpl +++ b/helm/defectdojo/README.md.gotmpl @@ -495,6 +495,22 @@ kubectl delete serviceAccount defectdojo kubectl delete pvc data-defectdojo-redis-0 data-defectdojo-postgresql-0 ``` +## Development/contribution + +In case you decide to help with the improvement of the HELM chart, keep in mind that values/descriptions might need to be adjusted in multiple places (see below). + +### HELM Docs update + +Documentation provided in the README file needs to contain the latest information from `values.yaml` and all other related assets. +If GitHub Action _Lint Helm chart / Update documentation_ step fails, install https://github.com/norwoodj/helm-docs and run locally `helm-docs --chart-search-root helm/deeefectdojo` before committing your changes. +The helm-docs documentation will be generated for you. + +### HELM Schema update + +The HELM structure supports the existence of a `values.schema.json` file. This file is used to validate all values provided by the user before Helm starts rendering templates. +The chart needs to have a `values.schema.json` file that is compatible with the default `values.yaml` file. +If GitHub Action _Lint Helm chart / Update schema_ step fails, install https://github.com/losisin/helm-values-schema-json and run locally `helm schema --use-helm-docs` in `helm/defectdojo` before committing your changes. +The HELM schema will be generated for you. # General information about chart values From 856aa7a089548489cb776e2b7f34c6afacf271ee Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:50:59 +0000 Subject: [PATCH 13/25] feat(renovate): Wait 2 days to use latest k8s (#13694) --- .github/renovate.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/renovate.json b/.github/renovate.json index a5ae6324179..8b51f3bfa3a 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -27,6 +27,17 @@ "matchDatasources": "github-releases", "matchPackageNames": "renovatebot/renovate", "schedule": ["* * * * 0"] + },{ + "description": "Minikube does not like freshly released k8s. We need to wait some time so it will be adopted", + "matchDatasources": [ + "custom.endoflife-oldest-maintained", + "github-releases" + ], + "matchPackageNames": [ + "kubernetes", + "kubernetes/kubernetes" + ], + "minimumReleaseAge": "2 days" }], "customDatasources": { "endoflife-oldest-maintained": { From 2171863f732f8eae9af5c5f2cf9422c8af230c80 Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:51:36 +0000 Subject: [PATCH 14/25] fix(helm/renovate/dependabot): Commit changes & fix condition format (#13695) --- .github/workflows/test-helm-chart.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-helm-chart.yml b/.github/workflows/test-helm-chart.yml index 5f59843c06c..23e93020778 100644 --- a/.github/workflows/test-helm-chart.yml +++ b/.github/workflows/test-helm-chart.yml @@ -115,6 +115,8 @@ jobs: if: startsWith(github.head_ref, 'renovate/') || startsWith(github.head_ref, 'dependabot/') run: | yq -i '.annotations."artifacthub.io/changes" += "- kind: changed\n description: ${{ github.event.pull_request.title }}\n"' helm/defectdojo/Chart.yaml + git add helm/defectdojo/Chart.yaml + git commit -m "ci: update Chart annotations from PR #${{ github.event.pull_request.number }}" || echo "No changes to commit" - name: Run helm-docs (update) uses: losisin/helm-docs-github-action@a57fae5676e4c55a228ea654a1bcaec8dd3cf5b5 # v1.6.2 @@ -128,7 +130,7 @@ jobs: # The helm-docs documentation will be generated for you. - name: Run helm-docs (check) uses: losisin/helm-docs-github-action@a57fae5676e4c55a228ea654a1bcaec8dd3cf5b5 # v1.6.2 - if: ! startsWith(github.head_ref, 'renovate/') || startsWith(github.head_ref, 'dependabot/') + if: ${{ !(startsWith(github.head_ref, 'renovate/') || startsWith(github.head_ref, 'dependabot/')) }} with: fail-on-diff: true chart-search-root: "helm/defectdojo" From ed83097c0f18d369d17f65adffec839f03a5fec1 Mon Sep 17 00:00:00 2001 From: valentijnscholten Date: Thu, 13 Nov 2025 20:19:33 +0100 Subject: [PATCH 15/25] reimport: support pro hash method (#13680) Co-authored-by: Valentijn Scholten --- dojo/importers/default_reimporter.py | 2 ++ dojo/models.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/dojo/importers/default_reimporter.py b/dojo/importers/default_reimporter.py index 07248cc21c6..4813d92453a 100644 --- a/dojo/importers/default_reimporter.py +++ b/dojo/importers/default_reimporter.py @@ -772,4 +772,6 @@ def calculate_unsaved_finding_hash_code( self, unsaved_finding: Finding, ) -> str: + # this is overridden in Pro, but will still call this via super() + deduplicationLogger.debug("Calculating hash code for unsaved finding") return unsaved_finding.compute_hash_code() diff --git a/dojo/models.py b/dojo/models.py index a06d2bde864..0f586dcfbb6 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -2908,6 +2908,11 @@ def risk_acceptance(self): return None def compute_hash_code(self): + # Allow Pro to overwrite compute hash_code which gets dedupe settings from a database instead of django.settings + from dojo.utils import get_custom_method # noqa: PLC0415 circular import + if compute_hash_code_method := get_custom_method("FINDING_COMPUTE_HASH_METHOD"): + deduplicationLogger.debug("using custom compute_hash_code method") + return compute_hash_code_method(self) # Check if all needed settings are defined if not hasattr(settings, "HASHCODE_FIELDS_PER_SCANNER") or not hasattr(settings, "HASHCODE_ALLOWS_NULL_CWE") or not hasattr(settings, "HASHCODE_ALLOWED_FIELDS"): From 3ce29cb2ce968e4724545312f6c84de714111ee5 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 14 Nov 2025 04:22:45 +0100 Subject: [PATCH 16/25] :bug: fix DD_EDITABLE_MITIGATED_DATA close finding internal server error #13699 (#13701) --- dojo/finding/views.py | 2 + unittests/test_system_settings.py | 62 ++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/dojo/finding/views.py b/dojo/finding/views.py index 9a944dccb60..e48554e613d 100644 --- a/dojo/finding/views.py +++ b/dojo/finding/views.py @@ -1145,12 +1145,14 @@ def close_finding(request, fid): note_type_activation = Note_Type.objects.filter(is_active=True) missing_note_types = get_missing_mandatory_notetypes(finding) if len(note_type_activation) else note_type_activation form = CloseFindingForm( + instance=finding, missing_note_types=missing_note_types, can_edit_mitigated_data=finding_helper.can_edit_mitigated_data(request.user), ) if request.method == "POST": form = CloseFindingForm( request.POST, + instance=finding, missing_note_types=missing_note_types, can_edit_mitigated_data=finding_helper.can_edit_mitigated_data(request.user), ) diff --git a/unittests/test_system_settings.py b/unittests/test_system_settings.py index a735ca03f98..d2e25630b0c 100644 --- a/unittests/test_system_settings.py +++ b/unittests/test_system_settings.py @@ -1,4 +1,17 @@ -from dojo.models import System_Settings +from django.test import TestCase, override_settings +from django.urls import reverse +from django.utils.timezone import now + +from dojo.models import ( + Engagement, + Finding, + Product, + Product_Type, + System_Settings, + Test, + Test_Type, + User, +) from .dojo_test_case import DojoTestCase @@ -26,3 +39,50 @@ def test_system_settings_update(self): system_settings.save() system_settings = System_Settings.objects.get(no_cache=True) self.assertEqual(system_settings.enable_jira, True) + + +@override_settings(DD_EDITABLE_MITIGATED_DATA=True) +class CloseFindingViewInstanceTest(TestCase): + def setUp(self): + self.user = User.objects.create_user( + username="tester", + password="pass", # noqa: S106 + is_staff=True, + is_superuser=True, + ) + self.client.force_login(self.user) + self.product_type = Product_Type.objects.create(name="Test Product Type") + self.product = Product.objects.create(name="Test Product", prod_type=self.product_type) + self.engagement = Engagement.objects.create( + name="Test Engagement", + product=self.product, + target_start=now(), + target_end=now(), + ) + self.test_type = Test_Type.objects.create(name="Unit Test Type") + self.test = Test.objects.create( + engagement=self.engagement, + test_type=self.test_type, + title="Test for Finding", + target_start=now(), + target_end=now(), + ) + self.finding = Finding.objects.create( + title="Close Finding Test", + active=True, + test=self.test, + reporter=self.user, + ) + self.url = reverse("close_finding", args=[self.finding.id]) + + def test_get_request_initializes_form_with_finding_instance(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + form = response.context["form"] + self.assertIsInstance(form.instance, Finding) + self.assertEqual(form.instance.id, self.finding.id) + + def test_post_request_initializes_form_with_finding_instance(self): + data = {"close_reason": "Mitigated", "notes": "Closing this finding"} + response = self.client.post(self.url, data) + self.assertIn(response.status_code, [200, 302]) From 769231d739fd0f9ded517e5cc46040959c410557 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Thu, 13 Nov 2025 20:23:24 -0700 Subject: [PATCH 17/25] :bug: add user mention notifications in note creation for Engagement, Finding, and Tests (#13696) --- dojo/api_v2/views.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index 2b2c7a36d2e..bdde57955f2 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -14,6 +14,7 @@ from django.db.models.query import QuerySet as DjangoQuerySet from django.http import FileResponse, HttpResponse from django.shortcuts import get_object_or_404 +from django.urls import reverse from django.utils import timezone from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.renderers import OpenApiJsonRenderer2 @@ -176,6 +177,7 @@ generate_file_response, get_setting, get_system_setting, + process_tag_notifications, ) logger = logging.getLogger(__name__) @@ -528,6 +530,15 @@ def notes(self, request, pk=None): ) note.save() engagement.notes.add(note) + # Determine if we need to send any notifications for user mentioned + process_tag_notifications( + request=request, + note=note, + parent_url=request.build_absolute_uri( + reverse("view_engagement", args=(engagement.id,)), + ), + parent_title=f"Engagement: {engagement.name}", + ) serialized_note = serializers.NoteSerializer( {"author": author, "entry": entry, "private": private}, @@ -1086,6 +1097,15 @@ def notes(self, request, pk=None): ) note.save() finding.notes.add(note) + # Determine if we need to send any notifications for user mentioned + process_tag_notifications( + request=request, + note=note, + parent_url=request.build_absolute_uri( + reverse("view_finding", args=(finding.id,)), + ), + parent_title=f"Finding: {finding.title}", + ) if finding.has_jira_issue: jira_helper.add_comment(finding, note) @@ -2135,6 +2155,15 @@ def notes(self, request, pk=None): ) note.save() test.notes.add(note) + # Determine if we need to send any notifications for user mentioned + process_tag_notifications( + request=request, + note=note, + parent_url=request.build_absolute_uri( + reverse("view_test", args=(test.id,)), + ), + parent_title=f"Test: {test.title}", + ) serialized_note = serializers.NoteSerializer( {"author": author, "entry": entry, "private": private}, From 44ebefb46fbb596b94fb09991055fae3434b2f9b Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 14 Nov 2025 04:25:52 +0100 Subject: [PATCH 18/25] :tada: Add pwn sast fix_available field (#13702) --- dojo/tools/pwn_sast/parser.py | 4 ++++ unittests/tools/test_pwn_sast_parser.py | 1 + 2 files changed, 5 insertions(+) diff --git a/dojo/tools/pwn_sast/parser.py b/dojo/tools/pwn_sast/parser.py index 89635ad8155..8be69874bbe 100644 --- a/dojo/tools/pwn_sast/parser.py +++ b/dojo/tools/pwn_sast/parser.py @@ -115,6 +115,10 @@ def get_findings(self, filename, test): file_path=offending_file, unique_id_from_tool=unique_finding_key, ) + if mitigation: + finding.fix_available = True + else: + finding.fix_available = False findings[unique_finding_key] = finding return list(findings.values()) diff --git a/unittests/tools/test_pwn_sast_parser.py b/unittests/tools/test_pwn_sast_parser.py index ff2762b146a..49feb32df3a 100644 --- a/unittests/tools/test_pwn_sast_parser.py +++ b/unittests/tools/test_pwn_sast_parser.py @@ -24,6 +24,7 @@ def test_parse_many_finding(self): findings = parser.get_findings(testfile, Test()) self.assertIsInstance(findings, list) self.assertEqual(3, len(findings)) + self.assertEqual(True, findings[0].fix_available) def test_one_dup_finding(self): with (get_unit_tests_scans_path("pwn_sast") / "one_dup_finding.json").open(encoding="utf-8") as testfile: From 3fb802b3dd6bc355a6e0a0afcb92dfe889d52623 Mon Sep 17 00:00:00 2001 From: Jino Tesauro Date: Fri, 14 Nov 2025 16:18:27 -0600 Subject: [PATCH 19/25] Qualys parser add CVEs to vulnerability ids for xml files --- dojo/tools/qualys/parser.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/dojo/tools/qualys/parser.py b/dojo/tools/qualys/parser.py index c629bcf28cd..a783f7af717 100644 --- a/dojo/tools/qualys/parser.py +++ b/dojo/tools/qualys/parser.py @@ -313,20 +313,28 @@ def parse_finding(host, tree): temp["CVSS_vector"] = None # CVE and LINKS - temp_cve_details = vuln_item.iterfind("CVE_ID_LIST/CVE_ID") + temp_cve_details = list(vuln_item.iterfind("CVE_ID_LIST/CVE_ID")) if temp_cve_details: - cl = { - cve_detail.findtext("ID"): cve_detail.findtext("URL") - for cve_detail in temp_cve_details - } - temp["cve"] = "\n".join(list(cl.keys())) - temp["links"] = "\n".join(list(cl.values())) + cve_list = [] + link_list = [] + for cve_detail in temp_cve_details: + cve_id = cve_detail.findtext("ID") + cve_url = cve_detail.findtext("URL") + if cve_id: + cve_list.append(cve_id) + if cve_url: + link_list.append(cve_url) + + temp["cve_list"] = cve_list # list of CVE strings + temp["links"] = "\n".join(link_list) + else: + temp["cve_list"] = [] # Generate severity from number in XML's 'SEVERITY' field, if not present default to 'Informational' sev = get_severity(vuln_item.findtext("SEVERITY")) finding = None if temp_cve_details: - refs = "\n".join(list(cl.values())) + refs = temp.get("links", "") finding = Finding( title="QID-" + gid[4:] + " | " + temp["vuln_name"], mitigation=temp["solution"], @@ -363,6 +371,7 @@ def parse_finding(host, tree): finding.verified = True finding.unsaved_endpoints = [] finding.unsaved_endpoints.append(ep) + finding.unsaved_vulnerability_ids = temp.get("cve_list", []) ret_rows.append(finding) return ret_rows From c8b521a672c7061a9b086eeddfa138347d660334 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:19:26 -0700 Subject: [PATCH 20/25] Add CVE assertions to Qualys parser tests for vulnerability IDs --- unittests/tools/test_qualys_parser.py | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/unittests/tools/test_qualys_parser.py b/unittests/tools/test_qualys_parser.py index b7d9f95b944..457588a70c0 100644 --- a/unittests/tools/test_qualys_parser.py +++ b/unittests/tools/test_qualys_parser.py @@ -151,10 +151,38 @@ def test_parse_file_with_cvss_values_and_scores(self): for finding in findings: if finding.unsaved_endpoints[0].host == "demo14.s02.sjc01.qualys.com" and finding.title == "QID-370876 | AMD Processors Multiple Security Vulnerabilities (RYZENFALL/MASTERKEY/CHIMERA-FW/FALLOUT)": finding_cvssv3_score = finding + self.assertEqual( + finding.unsaved_vulnerability_ids, + [ + "CVE-2018-8930", + "CVE-2018-8931", + "CVE-2018-8932", + "CVE-2018-8933", + "CVE-2018-8934", + "CVE-2018-8935", + "CVE-2018-8936", + ], + ) if finding.unsaved_endpoints[0].host == "demo13.s02.sjc01.qualys.com" and finding.title == "QID-370876 | AMD Processors Multiple Security Vulnerabilities (RYZENFALL/MASTERKEY/CHIMERA-FW/FALLOUT)": finding_no_cvssv3_at_detection = finding + self.assertEqual( + finding.unsaved_vulnerability_ids, + [ + "CVE-2018-8930", + "CVE-2018-8931", + "CVE-2018-8932", + "CVE-2018-8933", + "CVE-2018-8934", + "CVE-2018-8935", + "CVE-2018-8936", + ], + ) if finding.unsaved_endpoints[0].host == "demo14.s02.sjc01.qualys.com" and finding.title == 'QID-121695 | NTP "monlist" Feature Denial of Service Vulnerability': finding_no_cvssv3 = finding + self.assertEqual( + finding.unsaved_vulnerability_ids, + ["CVE-2013-5211"], + ) # The CVSS Vector is not used from the Knowledgebase self.assertEqual( # CVSS_FINAL is defined without a cvssv3 vector From 25939269a0c93d7d14500aa97e2d8eb1306497a4 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Mon, 17 Nov 2025 02:35:55 +0100 Subject: [PATCH 21/25] :bug: fix finding closed with a provided mitigated date #13699 (#13700) * :bug: fix finding closed with a provided mitigated date * advance unittests --- dojo/finding/helper.py | 26 ++++++--- unittests/test_finding_model.py | 94 +++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/dojo/finding/helper.py b/dojo/finding/helper.py index 95021e9575c..a1c7993d30f 100644 --- a/dojo/finding/helper.py +++ b/dojo/finding/helper.py @@ -1,5 +1,6 @@ import logging from contextlib import suppress +from datetime import datetime from time import strftime from django.conf import settings @@ -9,6 +10,7 @@ from django.dispatch.dispatcher import receiver from django.urls import reverse from django.utils import timezone +from django.utils.timezone import is_naive, make_aware, now from fieldsignals import pre_save_changed import dojo.jira_link.helper as jira_helper @@ -740,6 +742,17 @@ def save_vulnerability_ids_template(finding_template, vulnerability_ids): finding_template.cve = None +def normalize_datetime(value): + """Ensure value is timezone-aware datetime.""" + if value: + if not isinstance(value, datetime): + value = datetime.combine(value, datetime.min.time()) + # Make timezone-aware if naive + if is_naive(value): + value = make_aware(value) + return value + + def close_finding( *, finding, @@ -761,15 +774,16 @@ def close_finding( """ # Core status updates finding.is_mitigated = is_mitigated - now = timezone.now() - finding.mitigated = mitigated or now + current_time = now() + mitigated_date = normalize_datetime(mitigated) or current_time + finding.mitigated = mitigated_date finding.mitigated_by = mitigated_by or user finding.active = False finding.false_p = bool(false_p) finding.out_of_scope = bool(out_of_scope) finding.duplicate = bool(duplicate) finding.under_review = False - finding.last_reviewed = finding.mitigated + finding.last_reviewed = mitigated_date finding.last_reviewed_by = user # Create note if provided @@ -779,16 +793,16 @@ def close_finding( entry=note_entry, author=user, note_type=note_type, - date=finding.mitigated, + date=mitigated_date, ) finding.notes.add(new_note) # Endpoint statuses for status in finding.status_finding.all(): status.mitigated_by = finding.mitigated_by - status.mitigated_time = finding.mitigated + status.mitigated_time = mitigated_date status.mitigated = True - status.last_modified = timezone.now() + status.last_modified = current_time status.save() # Risk acceptance diff --git a/unittests/test_finding_model.py b/unittests/test_finding_model.py index bc65ffd1096..6de9e4847fc 100644 --- a/unittests/test_finding_model.py +++ b/unittests/test_finding_model.py @@ -1,8 +1,23 @@ -from datetime import datetime, timedelta +from datetime import date, datetime, timedelta from crum import impersonate - -from dojo.models import DojoMeta, Engagement, Finding, Test, User +from django.utils.timezone import is_naive, now + +from dojo.finding.helper import close_finding +from dojo.models import ( + DojoMeta, + Endpoint, + Endpoint_Status, + Engagement, + Finding, + Note_Type, + Notes, + Product, + Product_Type, + Test, + Test_Type, + User, +) from .dojo_test_case import DojoTestCase @@ -10,6 +25,79 @@ class TestFindingModel(DojoTestCase): fixtures = ["dojo_testdata.json"] + def setUp(self): + self.user = User.objects.first() # Use a user from fixtures + self.product_type = Product_Type.objects.create(name="Test Product Type") + self.product = Product.objects.create(name="Test Product", prod_type=self.product_type) + self.engagement = Engagement.objects.create( + name="Test Engagement", + product=self.product, + target_start=now(), + target_end=now(), + ) + self.test_type = Test_Type.objects.create(name="Unit Test Type") + self.test = Test.objects.create( + engagement=self.engagement, + test_type=self.test_type, + title="Test for Finding", + target_start=now(), + target_end=now(), + ) + self.finding = Finding.objects.create(title="Close Finding Test", active=True, test=self.test) + self.endpoint = Endpoint.objects.create(host="test.local") + self.endpoint_status = Endpoint_Status.objects.create(finding=self.finding, endpoint=self.endpoint) + self.finding.status_finding.add(self.endpoint_status) + + def test_close_finding_with_naive_date(self): + note_type_obj = Note_Type.objects.create(name="General") + naive_date = date.today() # No timezone + close_finding( + finding=self.finding, + user=self.user, + is_mitigated=True, + mitigated=naive_date, + mitigated_by=None, + false_p=False, + out_of_scope=False, + duplicate=False, + note_entry="Mitigation note", + note_type=note_type_obj, + ) + self.assertFalse(is_naive(self.finding.mitigated)) + note = Notes.objects.filter(finding=self.finding).first() + self.assertIsNotNone(note) + self.assertFalse(is_naive(note.date)) + status = Endpoint_Status.objects.filter(finding=self.finding).first() + self.assertTrue(status.mitigated) + self.assertFalse(is_naive(status.mitigated_time)) + + def test_close_finding_with_naive_datetime(self): + naive_datetime = datetime(2025, 11, 12, 0, 0, 0) + close_finding( + finding=self.finding, + user=self.user, + is_mitigated=True, + mitigated=naive_datetime, + mitigated_by=None, + false_p=False, + out_of_scope=False, + duplicate=False, + ) + self.assertFalse(is_naive(self.finding.mitigated)) + + def test_close_finding_with_none_mitigated(self): + close_finding( + finding=self.finding, + user=self.user, + is_mitigated=True, + mitigated=None, + mitigated_by=None, + false_p=False, + out_of_scope=False, + duplicate=False, + ) + self.assertFalse(is_naive(self.finding.mitigated)) + def test_get_sast_source_file_path_with_link_no_file_path(self): finding = Finding() self.assertEqual(None, finding.get_sast_source_file_path_with_link()) From 3d3427beecc1d3db497470f845ff4b66ddc9fa66 Mon Sep 17 00:00:00 2001 From: Paul Osinski <42211303+paulOsinski@users.noreply.github.com> Date: Sun, 16 Nov 2025 20:36:57 -0500 Subject: [PATCH 22/25] [docs] typo fixes (#13709) Co-authored-by: Paul Osinski --- .../content/en/connecting_your_tools/import_intro.md | 12 +++++------- .../product_hierarchy.md | 7 ++++--- docs/content/supported_tools/_index.md | 2 +- .../parsers/generic_findings_import.md | 4 +++- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/content/en/connecting_your_tools/import_intro.md b/docs/content/en/connecting_your_tools/import_intro.md index c0576c93aa1..44eb8fd2b44 100644 --- a/docs/content/en/connecting_your_tools/import_intro.md +++ b/docs/content/en/connecting_your_tools/import_intro.md @@ -4,18 +4,16 @@ description: "Learn how to import data manually, through the API, or via a conne weight: 1 --- -One of the things we understand at DefectDojo is that every company’s security needs are completely different. There is no ‘one\-size\-fits\-all’ approach. As your organization changes, having a flexible approach is key. - -DefectDojo allows you to connect your security tools in a flexible way to match those changes. +One of the things we understand at DefectDojo is that every company’s security needs are completely different. There is no one-size-fits-all approach. As your organization changes, having a flexible approach is key, and DefectDojo allows you to connect your security tools in a flexible way to match those changes. ## Scan Upload Methods -When DefectDojo receives a vulnerability report from a security tool, it will create Findings based on the vulnerabilities contained within that report. DefectDojo acts as the central repository for these Findings where they can be triaged, remediated or otherwise addressed by you and your team. +When DefectDojo receives a vulnerability report from a security tool, it will create Findings based on the vulnerabilities contained within that report. DefectDojo acts as the central repository for these Findings where they can be triaged, remediated, or otherwise addressed by you and your team. There are two main ways that DefectDojo can upload Finding reports. * Via direct **import** through the UI: [Import Scan Form](../import_scan_files/import_scan_ui) -* Via **API** endpoint (allowing for automated data ingest): See [API Docs](https://docs.defectdojo.com/en/api/api-v2-docs/) +* Via **API** endpoint (allowing for automated data ingestion): See [API Docs](https://docs.defectdojo.com/en/api/api-v2-docs/) #### DefectDojo Pro Methods @@ -29,8 +27,8 @@ There are two main ways that DefectDojo can upload Finding reports. | | **UI Import** | **API** | **Connectors** (Pro) | **Smart Upload** (Pro)| | --- | --- | --- | --- | --- | -| **Supported Scan Types** | All: see [Supported Tools](/supported_tools/) | All: see [Supported Tools](/supported_tools/) | Snyk, Semgrep, Burp Suite, AWS Security Hub, Probely, Checkmarx, Tenable | Nexpose, NMap, OpenVas, Qualys, Tenable | -| **Automation?** | Available via API: `/reimport` `/import` endpoints | Triggered from [CLI Importer](../external_tools) or external code | Connectors is inherently automated | Available via API: `/smart_upload_import` endpoint | +| **Supported Scan Types** | All: see [Supported Tools](/supported_tools/) | All: see [Supported Tools](/supported_tools/) | Anchore, AWS Security Hub, BurpSuite, Checkmarx ONE, Dependency-Track, Probely, Semgrep, SonarQube, Snyk, Tenable, Wiz | Nexpose, NMap, OpenVas, Qualys, Tenable | +| **Automation?** | Available via API: `/reimport` `/import` endpoints | Triggered from [CLI Importer](../external_tools) or external code | Connectors is an inherently automated feature | Available via API: `/smart_upload_import` endpoint | ### Product Hierarchy and organization diff --git a/docs/content/en/working_with_findings/organizing_engagements_tests/product_hierarchy.md b/docs/content/en/working_with_findings/organizing_engagements_tests/product_hierarchy.md index bff5e356e2f..d2105b75ac5 100644 --- a/docs/content/en/working_with_findings/organizing_engagements_tests/product_hierarchy.md +++ b/docs/content/en/working_with_findings/organizing_engagements_tests/product_hierarchy.md @@ -116,7 +116,7 @@ Tests always have: * an associated test **Environment** * an associated **Engagement** -Tests can be created in different ways. Tests can be automatically created when scan data is imported directly into to an Engagement, resulting in a new Test containing the scan data. Tests can also be created in anticipation of planning future engagements, or for manually entered security findings requiring tracking and remediation. +Tests can be created in different ways. Tests can be automatically created when scan data is imported directly into an Engagement, resulting in a new Test containing the scan data. Tests can also be created in anticipation of planning future engagements, or for manually entered security findings requiring tracking and remediation. ### **Test Types** @@ -124,8 +124,9 @@ DefectDojo supports two categories of Test Types: 1. **Parser-based Test Types**: These correspond to specific security scanners that produce output in formats like XML, JSON, or CSV. When importing scan results, DefectDojo uses specialized parsers to convert the scanner output into Findings. -2. **Non-parser Test Types**: These are used for manually created findings not imported from a scan files. -The following Test Types appear in the "Scan Type" dropdown when creating a new test, but will not appear when selecting "Import Scan": +2. **Non-parser Test Types**: These are used for manually created Findings not imported from scan files. These Test Types use the [Generic Findings Import](/supported_tools/parsers/generic_findings_import/) method to render Findings and metadata. + +The following Test Types appear in the "Scan Type" dropdown when creating a new test. * API Test * Static Check * Pen Test diff --git a/docs/content/supported_tools/_index.md b/docs/content/supported_tools/_index.md index 0429a2744b7..aaa71e1b65e 100644 --- a/docs/content/supported_tools/_index.md +++ b/docs/content/supported_tools/_index.md @@ -26,7 +26,7 @@ DefectDojo can parse data from 200+ security reports and counting. | [Connectors](/en/connecting_your_tools/connectors/about_connectors/): supported tools | [Smart Upload](/en/connecting_your_tools/import_scan_files/smart_upload/): supported tools | | --- | --- | -| AWS Security Hub, BurpSuite, Checkmarx ONE, Dependency-Track, Probely, Semgrep, SonarQube, Snyk, Tenable | Nexpose, NMap, OpenVas, Qualys, Tenable, Wiz | +| Anchore, AWS Security Hub, BurpSuite, Checkmarx ONE, Dependency-Track, Probely, Semgrep, SonarQube, Snyk, Tenable | Nexpose, NMap, OpenVas, Qualys, Tenable, Wiz | # All Supported Tools diff --git a/docs/content/supported_tools/parsers/generic_findings_import.md b/docs/content/supported_tools/parsers/generic_findings_import.md index 06c229ef2e1..a7b16002917 100644 --- a/docs/content/supported_tools/parsers/generic_findings_import.md +++ b/docs/content/supported_tools/parsers/generic_findings_import.md @@ -8,11 +8,13 @@ Open-source and Pro users can use Generic Findings Import as a method to ingest Using Generic Findings Import will create a new Test Type in your DefectDojo instance called "`{The Name Of Your Test}` (Generic Findings Import)". For example, this JSON content will result in a Test Type called "Example Report (Generic Findings Import)": +``` { "name": "Example Report", "findings": [] } +``` DefectDojo Pro users can also consider using the [Universal Parser](../universal_parser), a tool which allows for highly customizable JSON, XML and CSV imports. -For more information on supported parameters for Generic Findings Import, see the [Parser Guide](../file/generic) \ No newline at end of file +For more information on supported parameters for Generic Findings Import, see the related [Parser Guide](../file/generic). \ No newline at end of file From b5a7f9e8a687183cfbbe66abfd621b6ed3b0359c Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Sun, 16 Nov 2025 21:44:35 -0700 Subject: [PATCH 23/25] Refactor CVE extraction in parse_finding to use list comprehensions for improved readability --- dojo/tools/qualys/parser.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/dojo/tools/qualys/parser.py b/dojo/tools/qualys/parser.py index a783f7af717..0300ee43ff7 100644 --- a/dojo/tools/qualys/parser.py +++ b/dojo/tools/qualys/parser.py @@ -311,24 +311,10 @@ def parse_finding(host, tree): split_cvss(cvss2, temp) # DefectDojo does not support cvssv2 temp["CVSS_vector"] = None - # CVE and LINKS - temp_cve_details = list(vuln_item.iterfind("CVE_ID_LIST/CVE_ID")) - if temp_cve_details: - cve_list = [] - link_list = [] - for cve_detail in temp_cve_details: - cve_id = cve_detail.findtext("ID") - cve_url = cve_detail.findtext("URL") - if cve_id: - cve_list.append(cve_id) - if cve_url: - link_list.append(cve_url) - - temp["cve_list"] = cve_list # list of CVE strings - temp["links"] = "\n".join(link_list) - else: - temp["cve_list"] = [] + temp_cve_details = [(cve.findtext("ID"), cve.findtext("URL")) for cve in vuln_item.iterfind("CVE_ID_LIST/CVE_ID")] + temp["cve_list"] = [cve_id for cve_id, _ in temp_cve_details if cve_id] + temp["links"] = [url for _, url in temp_cve_details if url] # Generate severity from number in XML's 'SEVERITY' field, if not present default to 'Informational' sev = get_severity(vuln_item.findtext("SEVERITY")) From c1387b772e0a36e8a7247251381781eabfa4c303 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 17 Nov 2025 14:59:36 +0000 Subject: [PATCH 24/25] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 8 +++++--- helm/defectdojo/README.md | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/components/package.json b/components/package.json index 07c351cf814..cd38f67ae36 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.53.0-dev", + "version": "2.52.2", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index f9d9c59c502..effca246b4b 100644 --- a/dojo/__init__.py +++ b/dojo/__init__.py @@ -4,6 +4,6 @@ # Django starts so that shared_task will use this app. from .celery import app as celery_app # noqa: F401 -__version__ = "2.52.1" +__version__ = "2.52.2" __url__ = "https://github.com/DefectDojo/django-DefectDojo" __docs__ = "https://documentation.defectdojo.com" diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 4229d5281f1..70cb2841277 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.53.0-dev" +appVersion: "2.52.2" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.8.2-dev +version: 1.8.2 icon: https://defectdojo.com/hubfs/DefectDojo_favicon.png maintainers: - name: madchap @@ -33,7 +33,9 @@ dependencies: # - kind: security # description: Critical bug annotations: - artifacthub.io/prerelease: "true" + artifacthub.io/prerelease: "false" artifacthub.io/changes: | - kind: changed description: Location of HELM development hints has been changed + - kind: changed + description: Bump DefectDojo to 2.52.2 diff --git a/helm/defectdojo/README.md b/helm/defectdojo/README.md index d69d85c80c4..7e3d8421060 100644 --- a/helm/defectdojo/README.md +++ b/helm/defectdojo/README.md @@ -512,7 +512,7 @@ The HELM schema will be generated for you. # General information about chart values -![Version: 1.8.2-dev](https://img.shields.io/badge/Version-1.8.2--dev-informational?style=flat-square) ![AppVersion: 2.53.0-dev](https://img.shields.io/badge/AppVersion-2.53.0--dev-informational?style=flat-square) +![Version: 1.8.2](https://img.shields.io/badge/Version-1.8.2-informational?style=flat-square) ![AppVersion: 2.52.2](https://img.shields.io/badge/AppVersion-2.52.2-informational?style=flat-square) A Helm chart for Kubernetes to install DefectDojo From ed3719968e44dd211cdfac81f8a347f90181af9e Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 17 Nov 2025 15:43:08 +0000 Subject: [PATCH 25/25] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 12 ++++-------- helm/defectdojo/README.md | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/components/package.json b/components/package.json index cd38f67ae36..07c351cf814 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.52.2", + "version": "2.53.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index effca246b4b..75c2142e9d9 100644 --- a/dojo/__init__.py +++ b/dojo/__init__.py @@ -4,6 +4,6 @@ # Django starts so that shared_task will use this app. from .celery import app as celery_app # noqa: F401 -__version__ = "2.52.2" +__version__ = "2.53.0-dev" __url__ = "https://github.com/DefectDojo/django-DefectDojo" __docs__ = "https://documentation.defectdojo.com" diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 70cb2841277..4b57aec7bec 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.52.2" +appVersion: "2.53.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.8.2 +version: 1.8.3-dev icon: https://defectdojo.com/hubfs/DefectDojo_favicon.png maintainers: - name: madchap @@ -33,9 +33,5 @@ dependencies: # - kind: security # description: Critical bug annotations: - artifacthub.io/prerelease: "false" - artifacthub.io/changes: | - - kind: changed - description: Location of HELM development hints has been changed - - kind: changed - description: Bump DefectDojo to 2.52.2 + artifacthub.io/prerelease: "true" + artifacthub.io/changes: "" diff --git a/helm/defectdojo/README.md b/helm/defectdojo/README.md index 7e3d8421060..44d294b7ae6 100644 --- a/helm/defectdojo/README.md +++ b/helm/defectdojo/README.md @@ -512,7 +512,7 @@ The HELM schema will be generated for you. # General information about chart values -![Version: 1.8.2](https://img.shields.io/badge/Version-1.8.2-informational?style=flat-square) ![AppVersion: 2.52.2](https://img.shields.io/badge/AppVersion-2.52.2-informational?style=flat-square) +![Version: 1.8.3-dev](https://img.shields.io/badge/Version-1.8.3--dev-informational?style=flat-square) ![AppVersion: 2.53.0-dev](https://img.shields.io/badge/AppVersion-2.53.0--dev-informational?style=flat-square) A Helm chart for Kubernetes to install DefectDojo