From 8184c8163dfb30eafba3641fd416e1cd4ad11d50 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 7 Oct 2024 15:40:28 +0000 Subject: [PATCH 01/74] Update versions in application files --- components/package.json | 2 +- docs/content/en/getting_started/upgrading/2.40.md | 7 +++++++ dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 docs/content/en/getting_started/upgrading/2.40.md diff --git a/components/package.json b/components/package.json index ca4351fe41e..06cdce1889b 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.39.0", + "version": "2.40.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/docs/content/en/getting_started/upgrading/2.40.md b/docs/content/en/getting_started/upgrading/2.40.md new file mode 100644 index 00000000000..3420f9b8356 --- /dev/null +++ b/docs/content/en/getting_started/upgrading/2.40.md @@ -0,0 +1,7 @@ +--- +title: 'Upgrading to DefectDojo Version 2.40.x' +toc_hide: true +weight: -20241007 +description: No special instructions. +--- +There are no special instructions for upgrading to 2.40.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.40.0) for the contents of the release. diff --git a/dojo/__init__.py b/dojo/__init__.py index 6bc97e6bbb2..0dc36e95a1e 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.39.0" +__version__ = "2.40.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 b1927a7074a..4f2c96ba0fa 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.39.0" +appVersion: "2.40.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.153 +version: 1.6.154-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From 9766dc96c6dd747bf3ffd7681eac7d2774de3874 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:33:35 -0500 Subject: [PATCH 02/74] Bump boto3 from 1.35.33 to 1.35.34 (#11009) Bumps [boto3](https://github.com/boto/boto3) from 1.35.33 to 1.35.34. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.33...1.35.34) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 79d2844fc8f..57180e3f9d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.33 # Required for Celery Broker AWS (SQS) support +boto3==1.35.34 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.1 fontawesomefree==6.6.0 From 07e918abc3f5824b49b9068d5252f399ed59c22e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:33:56 -0500 Subject: [PATCH 03/74] Bump redis from 5.1.0 to 5.1.1 (#11008) Bumps [redis](https://github.com/redis/redis-py) from 5.1.0 to 5.1.1. - [Release notes](https://github.com/redis/redis-py/releases) - [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES) - [Commits](https://github.com/redis/redis-py/compare/v5.1.0...v5.1.1) --- updated-dependencies: - dependency-name: redis dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57180e3f9d9..c3134c757f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,7 +35,7 @@ psycopg[c]==3.2.3 cryptography==43.0.1 python-dateutil==2.9.0.post0 pytz==2024.2 -redis==5.1.0 +redis==5.1.1 requests==2.32.3 sqlalchemy==2.0.35 # Required by Celery broker transport urllib3==1.26.18 From 0a74312cd204f7ed77537b2571c0e18036b5e510 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:34:14 -0500 Subject: [PATCH 04/74] Bump humanize from 4.10.0 to 4.11.0 (#11007) Bumps [humanize](https://github.com/python-humanize/humanize) from 4.10.0 to 4.11.0. - [Release notes](https://github.com/python-humanize/humanize/releases) - [Commits](https://github.com/python-humanize/humanize/compare/4.10.0...4.11.0) --- updated-dependencies: - dependency-name: humanize dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3134c757f6..9615a932a50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,7 +24,7 @@ django-prometheus==2.3.1 Django==5.0.8 djangorestframework==3.15.2 html2text==2024.2.26 -humanize==4.10.0 +humanize==4.11.0 jira==3.8.0 PyGithub==1.58.2 lxml==5.3.0 From b7d296bcb146459139f2868045a171abebc02ed9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:34:35 -0500 Subject: [PATCH 05/74] Bump ruff from 0.6.8 to 0.6.9 (#11006) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.8 to 0.6.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.6.8...0.6.9) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index e2fd91d90a2..0e4ee0a0eae 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1 +1 @@ -ruff==0.6.8 \ No newline at end of file +ruff==0.6.9 \ No newline at end of file From 2ebbf76159abce411ccb271d2a14943bee330fc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:35:04 -0500 Subject: [PATCH 06/74] Bump nginx from 1.27.0-alpine to 1.27.2-alpine (#11005) Bumps nginx from 1.27.0-alpine to 1.27.2-alpine. --- updated-dependencies: - dependency-name: nginx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.nginx-alpine | 2 +- Dockerfile.nginx-debian | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.nginx-alpine b/Dockerfile.nginx-alpine index 0528e63047b..b1bd293b09d 100644 --- a/Dockerfile.nginx-alpine +++ b/Dockerfile.nginx-alpine @@ -140,7 +140,7 @@ COPY manage.py ./ COPY dojo/ ./dojo/ RUN env DD_SECRET_KEY='.' python3 manage.py collectstatic --noinput && true -FROM nginx:1.27.0-alpine@sha256:208b70eefac13ee9be00e486f79c695b15cef861c680527171a27d253d834be9 +FROM nginx:1.27.2-alpine@sha256:2140dad235c130ac861018a4e13a6bc8aea3a35f3a40e20c1b060d51a7efd250 ARG uid=1001 ARG appuser=defectdojo COPY --from=collectstatic /app/static/ /usr/share/nginx/html/static/ diff --git a/Dockerfile.nginx-debian b/Dockerfile.nginx-debian index b07ce5407de..f818e54c7f9 100644 --- a/Dockerfile.nginx-debian +++ b/Dockerfile.nginx-debian @@ -73,7 +73,7 @@ COPY dojo/ ./dojo/ RUN env DD_SECRET_KEY='.' python3 manage.py collectstatic --noinput && true -FROM nginx:1.27.0-alpine@sha256:208b70eefac13ee9be00e486f79c695b15cef861c680527171a27d253d834be9 +FROM nginx:1.27.2-alpine@sha256:2140dad235c130ac861018a4e13a6bc8aea3a35f3a40e20c1b060d51a7efd250 ARG uid=1001 ARG appuser=defectdojo COPY --from=collectstatic /app/static/ /usr/share/nginx/html/static/ From 0017f6a4f07406a92819e884fe80f6752cb1a8a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:51:17 -0500 Subject: [PATCH 07/74] Bump boto3 from 1.35.34 to 1.35.35 (#11021) Bumps [boto3](https://github.com/boto/boto3) from 1.35.34 to 1.35.35. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.34...1.35.35) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9615a932a50..edc8633b8e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.34 # Required for Celery Broker AWS (SQS) support +boto3==1.35.35 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.1 fontawesomefree==6.6.0 From 1fdb05a94a3bdf8e900f770a5fa0717a7f8cb14c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:51:36 -0500 Subject: [PATCH 08/74] Bump vcrpy from 6.0.1 to 6.0.2 (#11022) Bumps [vcrpy](https://github.com/kevin1024/vcrpy) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/kevin1024/vcrpy/releases) - [Changelog](https://github.com/kevin1024/vcrpy/blob/master/docs/changelog.rst) - [Commits](https://github.com/kevin1024/vcrpy/compare/v6.0.1...v6.0.2) --- updated-dependencies: - dependency-name: vcrpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index edc8633b8e0..08a3a83e4d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -54,7 +54,7 @@ JSON-log-formatter==1.1 django-split-settings==1.3.2 django-debug-toolbar==4.4.6 django-debug-toolbar-request-history==0.1.4 -vcrpy==6.0.1 +vcrpy==6.0.2 vcrpy-unittest==0.1.7 django-tagulous==2.1.0 PyJWT==2.9.0 From 60db54e1e1b706db52af4102b984c312bf364b03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:32:27 -0500 Subject: [PATCH 09/74] Bump pdfmake from 0.2.13 to 0.2.14 in /components (#11028) Bumps [pdfmake](https://github.com/bpampuch/pdfmake) from 0.2.13 to 0.2.14. - [Release notes](https://github.com/bpampuch/pdfmake/releases) - [Changelog](https://github.com/bpampuch/pdfmake/blob/0.2.14/CHANGELOG.md) - [Commits](https://github.com/bpampuch/pdfmake/compare/0.2.13...0.2.14) --- updated-dependencies: - dependency-name: pdfmake dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- components/package.json | 2 +- components/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/package.json b/components/package.json index 06cdce1889b..7d7c9d1b857 100644 --- a/components/package.json +++ b/components/package.json @@ -35,7 +35,7 @@ "metismenu": "~3.0.7", "moment": "^2.30.1", "morris.js": "morrisjs/morris.js", - "pdfmake": "^0.2.13", + "pdfmake": "^0.2.14", "startbootstrap-sb-admin-2": "1.0.7" }, "engines": { diff --git a/components/yarn.lock b/components/yarn.lock index 7bb19365790..952d09ff22a 100644 --- a/components/yarn.lock +++ b/components/yarn.lock @@ -824,10 +824,10 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -pdfmake@^0.2.13: - version "0.2.13" - resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.2.13.tgz#ea43fe9f0c8de1e5ec7b08486d6f4f8bbb8619e4" - integrity sha512-qeVE9Bzjm0oPCitH4/HYM/XCGTwoeOAOVAXPnV3s0kpPvTLkTF/bAF4jzorjkaIhXGQhzYk6Xclt0hMDYLY93w== +pdfmake@^0.2.14: + version "0.2.14" + resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.2.14.tgz#a257a393b54917218add829bff8e490be21e8077" + integrity sha512-x9gXFAY37/CAC/WaZB/683E4Pi0cVW/RMTTNxMpe4I2kRsKv8AE3Pz6+n7iTfn+84/GtSg99BjZkYh7oGFCKmg== dependencies: "@foliojs-fork/linebreak" "^1.1.1" "@foliojs-fork/pdfkit" "^0.14.0" From db7d9a0fc592662265c8855657c2ddfa5b82db22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:33:46 -0500 Subject: [PATCH 10/74] Bump python-gitlab from 4.12.2 to 4.13.0 (#11027) Bumps [python-gitlab](https://github.com/python-gitlab/python-gitlab) from 4.12.2 to 4.13.0. - [Release notes](https://github.com/python-gitlab/python-gitlab/releases) - [Changelog](https://github.com/python-gitlab/python-gitlab/blob/main/CHANGELOG.md) - [Commits](https://github.com/python-gitlab/python-gitlab/compare/v4.12.2...v4.13.0) --- updated-dependencies: - dependency-name: python-gitlab dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 08a3a83e4d7..e4b67867cf1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,7 +46,7 @@ titlecase==2.4.1 social-auth-app-django==5.4.2 social-auth-core==4.5.4 gitpython==3.1.43 -python-gitlab==4.12.2 +python-gitlab==4.13.0 cpe==1.3.1 packageurl-python==0.15.6 django-crum==0.7.9 From 535bb3b5696f4a325496ef3bcf5f078d26046ab5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:34:05 -0500 Subject: [PATCH 11/74] Bump boto3 from 1.35.35 to 1.35.36 (#11026) Bumps [boto3](https://github.com/boto/boto3) from 1.35.35 to 1.35.36. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.35...1.35.36) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e4b67867cf1..3e4b52d094d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.35 # Required for Celery Broker AWS (SQS) support +boto3==1.35.36 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.1 fontawesomefree==6.6.0 From bd507d322c6c6e9e9fd856ab285b6d2df9c4ac02 Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:15:53 +0200 Subject: [PATCH 12/74] Ruff: Fix issues via "target-version" (#10846) --- dojo/api_v2/serializers.py | 5 +- dojo/apps.py | 2 +- dojo/engagement/views.py | 30 +++++------ dojo/finding/views.py | 23 ++++----- dojo/finding_group/views.py | 3 +- dojo/forms.py | 5 +- dojo/home/views.py | 3 +- dojo/importers/auto_create_context.py | 30 +++++------ dojo/importers/base_importer.py | 51 +++++++++---------- dojo/importers/default_importer.py | 21 ++++---- dojo/importers/default_reimporter.py | 33 ++++++------ dojo/importers/endpoint_manager.py | 19 ++++--- dojo/importers/options.py | 13 ++--- dojo/metrics/utils.py | 11 ++-- dojo/models.py | 3 +- dojo/remote_user.py | 3 +- dojo/reports/views.py | 24 +++------ dojo/risk_acceptance/api.py | 4 +- dojo/system_settings/views.py | 3 +- dojo/test/views.py | 5 +- .../engines/appcheck.py | 3 +- .../engines/base.py | 22 ++++---- .../engines/nmap.py | 4 +- dojo/tools/blackduck/importer.py | 2 +- .../blackduck_binary_analysis/importer.py | 2 +- dojo/tools/checkmarx_one/parser.py | 13 +++-- .../parser.py | 3 +- dojo/tools/kics/parser.py | 3 +- dojo/tools/sarif/parser.py | 5 +- dojo/tools/tenable/xml_format.py | 2 +- dojo/tools/veracode/json_parser.py | 2 +- dojo/tools/whitehat_sentinel/parser.py | 7 ++- dojo/utils.py | 4 +- ruff.toml | 3 ++ unittests/test_bulk_risk_acceptance_api.py | 22 ++++---- unittests/test_dashboard.py | 5 +- unittests/test_finding_helper.py | 6 +-- unittests/test_flush_auditlog.py | 8 +-- unittests/test_import_reimport.py | 4 +- unittests/test_metrics_queries.py | 42 +++++++-------- unittests/test_risk_acceptance.py | 6 +-- unittests/test_utils_deduplication_reopen.py | 2 +- unittests/tools/test_arachni_parser.py | 2 +- unittests/tools/test_bugcrowd_parser.py | 4 +- .../tools/test_dependency_check_parser.py | 4 +- unittests/tools/test_sarif_parser.py | 10 ++-- unittests/tools/test_stackhawk_parser.py | 2 +- 47 files changed, 227 insertions(+), 256 deletions(-) diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index 471dfc019b5..eed696a2c95 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -3,7 +3,6 @@ import os import re from datetime import datetime -from typing import List import six import tagulous @@ -1517,7 +1516,7 @@ def get_engagement(self, obj): ) def validate(self, data): - def validate_findings_have_same_engagement(finding_objects: List[Finding]): + def validate_findings_have_same_engagement(finding_objects: list[Finding]): engagements = finding_objects.values_list("test__engagement__id", flat=True).distinct().count() if engagements > 1: msg = "You are not permitted to add findings from multiple engagements" @@ -2043,7 +2042,7 @@ def get_findings_count(self, obj) -> int: return obj.findings_count # TODO: maybe extend_schema_field is needed here? - def get_findings_list(self, obj) -> List[int]: + def get_findings_list(self, obj) -> list[int]: return obj.open_findings_list diff --git a/dojo/apps.py b/dojo/apps.py index fd3a06575fd..4d4d07af50e 100644 --- a/dojo/apps.py +++ b/dojo/apps.py @@ -98,5 +98,5 @@ def get_model_fields(default_fields, extra_fields=()): def get_model_default_fields(model): return tuple( field.name for field in model._meta.fields if - isinstance(field, (models.CharField, models.TextField)) + isinstance(field, models.CharField | models.TextField) ) diff --git a/dojo/engagement/views.py b/dojo/engagement/views.py index ea73bd80c63..54781eed409 100644 --- a/dojo/engagement/views.py +++ b/dojo/engagement/views.py @@ -7,7 +7,6 @@ from functools import reduce from tempfile import NamedTemporaryFile from time import strftime -from typing import List, Optional, Tuple from django.conf import settings from django.contrib import messages @@ -427,7 +426,7 @@ def get_risks_accepted(self, eng): def get_filtered_tests( self, request: HttpRequest, - queryset: List[Test], + queryset: list[Test], engagement: Engagement, ): filter_string_matching = get_system_setting("filter_string_matching", False) @@ -710,9 +709,9 @@ def get_development_environment( def get_engagement_or_product( self, user: Dojo_User, - engagement_id: Optional[int] = None, - product_id: Optional[int] = None, - ) -> Tuple[Engagement, Product, Product | Engagement]: + engagement_id: int | None = None, + product_id: int | None = None, + ) -> tuple[Engagement, Product, Product | Engagement]: """Using the path parameters, either fetch the product or engagement""" engagement = product = engagement_or_product = None # Get the product if supplied @@ -769,7 +768,7 @@ def get_jira_form( self, request: HttpRequest, engagement_or_product: Engagement | Product, - ) -> Tuple[JIRAImportScanForm | None, bool]: + ) -> tuple[JIRAImportScanForm | None, bool]: """Returns a JiraImportScanForm if jira is enabled""" jira_form = None push_all_jira_issues = False @@ -794,7 +793,7 @@ def get_product_tab( self, product: Product, engagement: Engagement, - ) -> Tuple[Product_Tab, dict]: + ) -> tuple[Product_Tab, dict]: """ Determine how the product tab will be rendered, and what tab will be selected as currently active @@ -811,9 +810,9 @@ def get_product_tab( def handle_request( self, request: HttpRequest, - engagement_id: Optional[int] = None, - product_id: Optional[int] = None, - ) -> Tuple[HttpRequest, dict]: + engagement_id: int | None = None, + product_id: int | None = None, + ) -> tuple[HttpRequest, dict]: """ Process the common behaviors between request types, and then return the request and context dict back to be rendered @@ -1046,8 +1045,8 @@ def failure_redirect( def get( self, request: HttpRequest, - engagement_id: Optional[int] = None, - product_id: Optional[int] = None, + engagement_id: int | None = None, + product_id: int | None = None, ) -> HttpResponse: """Process GET requests for the Import View""" # process the request and path parameters @@ -1062,8 +1061,8 @@ def get( def post( self, request: HttpRequest, - engagement_id: Optional[int] = None, - product_id: Optional[int] = None, + engagement_id: int | None = None, + product_id: int | None = None, ) -> HttpResponse: """Process POST requests for the Import View""" # process the request and path parameters @@ -1555,8 +1554,7 @@ def get_engagements(request): if not url: msg = "Please use the export button when exporting engagements" raise ValidationError(msg) - if url.startswith("url="): - url = url[4:] + url = url.removeprefix("url=") path_items = list(filter(None, re.split(r"/|\?", url))) diff --git a/dojo/finding/views.py b/dojo/finding/views.py index ea5578ee460..0c0d78d6cc5 100644 --- a/dojo/finding/views.py +++ b/dojo/finding/views.py @@ -7,7 +7,6 @@ import mimetypes from collections import OrderedDict, defaultdict from itertools import chain -from typing import Optional from django.conf import settings from django.contrib import messages @@ -265,9 +264,9 @@ class BaseListFindings: def __init__( self, filter_name: str = "All", - product_id: Optional[int] = None, - engagement_id: Optional[int] = None, - test_id: Optional[int] = None, + product_id: int | None = None, + engagement_id: int | None = None, + test_id: int | None = None, order_by: str = "numerical_severity", prefetch_type: str = "all", ): @@ -420,7 +419,7 @@ def add_breadcrumbs(self, request: HttpRequest, context: dict): return request, context - def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement_id: Optional[int] = None): + def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None): # Store the product and engagement ids self.product_id = product_id self.engagement_id = engagement_id @@ -446,43 +445,43 @@ def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement class ListOpenFindings(ListFindings): - def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement_id: Optional[int] = None): + def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None): self.filter_name = "Open" return super().get(request, product_id=product_id, engagement_id=engagement_id) class ListVerifiedFindings(ListFindings): - def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement_id: Optional[int] = None): + def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None): self.filter_name = "Verified" return super().get(request, product_id=product_id, engagement_id=engagement_id) class ListOutOfScopeFindings(ListFindings): - def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement_id: Optional[int] = None): + def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None): self.filter_name = "Out of Scope" return super().get(request, product_id=product_id, engagement_id=engagement_id) class ListFalsePositiveFindings(ListFindings): - def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement_id: Optional[int] = None): + def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None): self.filter_name = "False Positive" return super().get(request, product_id=product_id, engagement_id=engagement_id) class ListInactiveFindings(ListFindings): - def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement_id: Optional[int] = None): + def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None): self.filter_name = "Inactive" return super().get(request, product_id=product_id, engagement_id=engagement_id) class ListAcceptedFindings(ListFindings): - def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement_id: Optional[int] = None): + def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None): self.filter_name = "Accepted" return super().get(request, product_id=product_id, engagement_id=engagement_id) class ListClosedFindings(ListFindings): - def get(self, request: HttpRequest, product_id: Optional[int] = None, engagement_id: Optional[int] = None): + def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None): self.filter_name = "Closed" self.order_by = "-mitigated" return super().get(request, product_id=product_id, engagement_id=engagement_id) diff --git a/dojo/finding_group/views.py b/dojo/finding_group/views.py index 546dae93763..814d88888ed 100644 --- a/dojo/finding_group/views.py +++ b/dojo/finding_group/views.py @@ -74,8 +74,7 @@ def view_finding_group(request, fgid): if jira_issue: # See if the submitted issue was a issue key or the full URL jira_instance = jira_helper.get_jira_project(finding_group).jira_instance - if jira_issue.startswith(jira_instance.url + "/browse/"): - jira_issue = jira_issue[len(jira_instance.url + "/browse/"):] + jira_issue = jira_issue.removeprefix(jira_instance.url + "/browse/") if finding_group.has_jira_issue and not jira_issue == jira_helper.get_jira_key(finding_group): jira_helper.unlink_jira(request, finding_group) diff --git a/dojo/forms.py b/dojo/forms.py index 1dd52671c45..fcd37a467d7 100644 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -2382,10 +2382,7 @@ def get_jira_issue_template_dir_choices(): # template_list.append((os.path.join(base_dir, filename), filename)) for dirname in dirnames: - if base_dir.startswith(settings.TEMPLATE_DIR_PREFIX): - clean_base_dir = base_dir[len(settings.TEMPLATE_DIR_PREFIX):] - else: - clean_base_dir = base_dir + clean_base_dir = base_dir.removeprefix(settings.TEMPLATE_DIR_PREFIX) template_dir_list.append((os.path.join(clean_base_dir, dirname), dirname)) logger.debug("templates: %s", template_dir_list) diff --git a/dojo/home/views.py b/dojo/home/views.py index 2c4d16fadef..67e90bec106 100644 --- a/dojo/home/views.py +++ b/dojo/home/views.py @@ -1,6 +1,5 @@ from collections import defaultdict from datetime import timedelta -from typing import Dict from dateutil.relativedelta import relativedelta from django.db.models import Count, Q @@ -75,7 +74,7 @@ def support(request: HttpRequest) -> HttpResponse: return render(request, "dojo/support.html", {}) -def get_severities_all(findings) -> Dict[str, int]: +def get_severities_all(findings) -> dict[str, int]: severities_all = findings.values("severity").annotate(count=Count("severity")).order_by() return defaultdict(lambda: 0, {s["severity"]: s["count"] for s in severities_all}) diff --git a/dojo/importers/auto_create_context.py b/dojo/importers/auto_create_context.py index 16454568624..7d54782a676 100644 --- a/dojo/importers/auto_create_context.py +++ b/dojo/importers/auto_create_context.py @@ -1,6 +1,6 @@ import logging from datetime import datetime, timedelta -from typing import Any, Optional +from typing import Any from crum import get_current_user from django.db import transaction @@ -113,7 +113,7 @@ def process_import_meta_data_from_dict( """ def get_target_product_type_if_exists( self, - product_type_name: Optional[str] = None, + product_type_name: str | None = None, **kwargs: dict, ) -> Product_Type | None: """ @@ -128,8 +128,8 @@ def get_target_product_type_if_exists( def get_target_product_if_exists( self, - product_name: Optional[str] = None, - product_type_name: Optional[str] = None, + product_name: str | None = None, + product_type_name: str | None = None, **kwargs: dict, ) -> Product | None: """ @@ -168,7 +168,7 @@ def get_target_product_by_id_if_exists( def get_target_engagement_if_exists( self, engagement_id: int = 0, - engagement_name: Optional[str] = None, + engagement_name: str | None = None, product: Product = None, **kwargs: dict, ) -> Engagement | None: @@ -191,8 +191,8 @@ def get_target_engagement_if_exists( def get_target_test_if_exists( self, test_id: int = 0, - test_title: Optional[str] = None, - scan_type: Optional[str] = None, + test_title: str | None = None, + scan_type: str | None = None, engagement: Engagement = None, **kwargs: dict, ) -> Test | None: @@ -220,7 +220,7 @@ def get_target_test_if_exists( """ def get_or_create_product_type( self, - product_type_name: Optional[str] = None, + product_type_name: str | None = None, **kwargs: dict, ) -> Product_Type: """ @@ -243,8 +243,8 @@ def get_or_create_product_type( def get_or_create_product( self, - product_name: Optional[str] = None, - product_type_name: Optional[str] = None, + product_name: str | None = None, + product_type_name: str | None = None, *, auto_create_context: bool = False, **kwargs: dict, @@ -278,14 +278,14 @@ def get_or_create_product( def get_or_create_engagement( self, engagement_id: int = 0, - engagement_name: Optional[str] = None, - product_name: Optional[str] = None, - product_type_name: Optional[str] = None, + engagement_name: str | None = None, + product_name: str | None = None, + product_type_name: str | None = None, *, auto_create_context: bool = False, deduplication_on_engagement: bool = False, - source_code_management_uri: Optional[str] = None, - target_end: Optional[datetime] = None, + source_code_management_uri: str | None = None, + target_end: datetime | None = None, **kwargs: dict, ) -> Engagement: """Fetches an engagement by name or ID if one already exists.""" diff --git a/dojo/importers/base_importer.py b/dojo/importers/base_importer.py index ebd97fc37f6..c9a77fbb95b 100644 --- a/dojo/importers/base_importer.py +++ b/dojo/importers/base_importer.py @@ -1,6 +1,5 @@ import base64 import logging -from typing import List, Tuple from django.conf import settings from django.core.exceptions import ValidationError @@ -42,7 +41,7 @@ class Parser: and is purely for the sake of type hinting """ - def get_findings(scan_type: str, test: Test) -> List[Finding]: + def get_findings(scan_type: str, test: Test) -> list[Finding]: """ Stub function to make the hinting happier. The actual class is loosely obligated to have this function defined. @@ -89,7 +88,7 @@ def process_scan( scan: TemporaryUploadedFile, *args: list, **kwargs: dict, - ) -> Tuple[Test, int, int, int, int, int, Test_Import]: + ) -> tuple[Test, int, int, int, int, int, Test_Import]: """ A helper method that executes the entire import process in a single method. This includes parsing the file, processing the findings, and returning the @@ -99,9 +98,9 @@ def process_scan( def process_findings( self, - parsed_findings: List[Finding], + parsed_findings: list[Finding], **kwargs: dict, - ) -> List[Finding]: + ) -> list[Finding]: """ Make the conversion from unsaved Findings in memory to Findings that are saved in the database with and ID associated with them. This processor will also save any associated @@ -111,9 +110,9 @@ def process_findings( def close_old_findings( self, - findings: List[Finding], + findings: list[Finding], **kwargs: dict, - ) -> List[Finding]: + ) -> list[Finding]: """ Identify any findings that have been imported before, but are no longer present in later reports so that @@ -147,7 +146,7 @@ def parse_findings_static_test_type( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Parse the scan report submitted with the parser class and generate some findings that are not saved to the database yet. This step is crucial in determining if @@ -168,7 +167,7 @@ def parse_dynamic_test_type_tests( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Test]: + ) -> list[Test]: """Use the API configuration object to get the tests to be used by the parser""" try: return parser.get_tests(self.scan_type, scan) @@ -178,8 +177,8 @@ def parse_dynamic_test_type_tests( def parse_dynamic_test_type_findings_from_tests( self, - tests: List[Test], - ) -> List[Finding]: + tests: list[Test], + ) -> list[Finding]: """ currently we only support import one Test so for parser that support multiple tests (like SARIF) @@ -194,7 +193,7 @@ def parse_findings_dynamic_test_type( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Use the API configuration object to get the tests to be used by the parser to dump findings into @@ -208,7 +207,7 @@ def parse_findings( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Determine how to parse the findings based on the presence of the `get_tests` function on the parser object @@ -221,9 +220,9 @@ def parse_findings( def sync_process_findings( self, - parsed_findings: List[Finding], + parsed_findings: list[Finding], **kwargs: dict, - ) -> Tuple[List[Finding], List[Finding], List[Finding], List[Finding]]: + ) -> tuple[list[Finding], list[Finding], list[Finding], list[Finding]]: """ Processes findings in a synchronous manner such that all findings will be processed in a worker/process/thread @@ -232,9 +231,9 @@ def sync_process_findings( def async_process_findings( self, - parsed_findings: List[Finding], + parsed_findings: list[Finding], **kwargs: dict, - ) -> List[Finding]: + ) -> list[Finding]: """ Processes findings in chunks within N number of processes. The ASYNC_FINDING_IMPORT_CHUNK_SIZE setting will determine how many @@ -244,9 +243,9 @@ def async_process_findings( def determine_process_method( self, - parsed_findings: List[Finding], + parsed_findings: list[Finding], **kwargs: dict, - ) -> List[Finding]: + ) -> list[Finding]: """ Determines whether to process the scan iteratively, or in chunks, based upon the ASYNC_FINDING_IMPORT setting @@ -318,10 +317,10 @@ def update_test_tags(self): def update_import_history( self, - new_findings: List[Finding] = [], - closed_findings: List[Finding] = [], - reactivated_findings: List[Finding] = [], - untouched_findings: List[Finding] = [], + new_findings: list[Finding] = [], + closed_findings: list[Finding] = [], + reactivated_findings: list[Finding] = [], + untouched_findings: list[Finding] = [], ) -> Test_Import: """Creates a record of the import or reimport operation that has occurred.""" # Quick fail check to determine if we even wanted this @@ -447,9 +446,9 @@ def construct_imported_message( def chunk_findings( self, - finding_list: List[Finding], + finding_list: list[Finding], chunk_size: int = settings.ASYNC_FINDING_IMPORT_CHUNK_SIZE, - ) -> List[List[Finding]]: + ) -> list[list[Finding]]: """ Split a single large list into a list of lists of size `chunk_size`. For Example @@ -627,7 +626,7 @@ def process_request_response_pairs( def process_endpoints( self, finding: Finding, - endpoints_to_add: List[Endpoint], + endpoints_to_add: list[Endpoint], ) -> None: """ Process any endpoints to add to the finding. Endpoints could come from two places diff --git a/dojo/importers/default_importer.py b/dojo/importers/default_importer.py index 28a2de1e30d..95254ef59b8 100644 --- a/dojo/importers/default_importer.py +++ b/dojo/importers/default_importer.py @@ -1,5 +1,4 @@ import logging -from typing import List, Tuple from django.core.files.uploadedfile import TemporaryUploadedFile from django.core.serializers import deserialize, serialize @@ -86,7 +85,7 @@ def process_scan( scan: TemporaryUploadedFile, *args: list, **kwargs: dict, - ) -> Tuple[Test, int, int, int, int, int, Test_Import]: + ) -> tuple[Test, int, int, int, int, int, Test_Import]: """ The full step process of taking a scan report, and converting it to findings in the database. This entails the the following actions: @@ -143,9 +142,9 @@ def process_scan( def process_findings( self, - parsed_findings: List[Finding], + parsed_findings: list[Finding], **kwargs: dict, - ) -> List[Finding]: + ) -> list[Finding]: """ Saves findings in memory that were parsed from the scan report into the database. This process involves first saving associated objects such as endpoints, files, @@ -233,9 +232,9 @@ def process_findings( def close_old_findings( self, - findings: List[Finding], + findings: list[Finding], **kwargs: dict, - ) -> List[Finding]: + ) -> list[Finding]: """ Closes old findings based on a hash code match at either the product or the engagement scope. Closing an old finding entails setting the @@ -300,7 +299,7 @@ def parse_findings( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Determine how to parse the findings based on the presence of the `get_tests` function on the parser object @@ -318,7 +317,7 @@ def parse_findings_static_test_type( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Creates a test object as part of the import process as there is not one present at the time of import. Once the test is created, proceed with the traditional @@ -334,7 +333,7 @@ def parse_findings_dynamic_test_type( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Uses the parser to fetch any tests that may have been created by the API based parser, aggregates all findings from each test @@ -377,9 +376,9 @@ def parse_findings_dynamic_test_type( def async_process_findings( self, - parsed_findings: List[Finding], + parsed_findings: list[Finding], **kwargs: dict, - ) -> List[Finding]: + ) -> list[Finding]: """ Processes findings in chunks within N number of processes. The ASYNC_FINDING_IMPORT_CHUNK_SIZE setting will determine how many diff --git a/dojo/importers/default_reimporter.py b/dojo/importers/default_reimporter.py index 7f1c3bd15bc..9debf4aabaa 100644 --- a/dojo/importers/default_reimporter.py +++ b/dojo/importers/default_reimporter.py @@ -1,5 +1,4 @@ import logging -from typing import List, Tuple from django.core.files.uploadedfile import TemporaryUploadedFile from django.core.serializers import deserialize, serialize @@ -73,7 +72,7 @@ def process_scan( scan: TemporaryUploadedFile, *args: list, **kwargs: dict, - ) -> Tuple[Test, int, int, int, int, int, Test_Import]: + ) -> tuple[Test, int, int, int, int, int, Test_Import]: """ The full step process of taking a scan report, and converting it to findings in the database. This entails the the following actions: @@ -158,9 +157,9 @@ def determine_deduplication_algorithm(self) -> str: def process_findings( self, - parsed_findings: List[Finding], + parsed_findings: list[Finding], **kwargs: dict, - ) -> Tuple[List[Finding], List[Finding], List[Finding], List[Finding]]: + ) -> tuple[list[Finding], list[Finding], list[Finding], list[Finding]]: """ Saves findings in memory that were parsed from the scan report into the database. This process involves first saving associated objects such as endpoints, files, @@ -256,9 +255,9 @@ def process_findings( def close_old_findings( self, - findings: List[Finding], + findings: list[Finding], **kwargs: dict, - ) -> List[Finding]: + ) -> list[Finding]: """ Updates the status of findings that were detected as "old" by the reimport process findings methods @@ -289,7 +288,7 @@ def parse_findings( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Determine how to parse the findings based on the presence of the `get_tests` function on the parser object @@ -307,7 +306,7 @@ def parse_findings_static_test_type( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Parses the findings from file and assigns them to the test that was supplied @@ -320,7 +319,7 @@ def parse_findings_dynamic_test_type( self, scan: TemporaryUploadedFile, parser: Parser, - ) -> List[Finding]: + ) -> list[Finding]: """ Uses the parser to fetch any tests that may have been created by the API based parser, aggregates all findings from each test @@ -331,9 +330,9 @@ def parse_findings_dynamic_test_type( def async_process_findings( self, - parsed_findings: List[Finding], + parsed_findings: list[Finding], **kwargs: dict, - ) -> Tuple[List[Finding], List[Finding], List[Finding], List[Finding]]: + ) -> tuple[list[Finding], list[Finding], list[Finding], list[Finding]]: """ Processes findings in chunks within N number of processes. The ASYNC_FINDING_IMPORT_CHUNK_SIZE setting will determine how many @@ -388,7 +387,7 @@ def async_process_findings( def match_new_finding_to_existing_finding( self, unsaved_finding: Finding, - ) -> List[Finding]: + ) -> list[Finding]: """Matches a single new finding to N existing findings and then returns those matches""" # This code should match the logic used for deduplication out of the re-import feature. # See utils.py deduplicate_* functions @@ -429,7 +428,7 @@ def process_matched_finding( self, unsaved_finding: Finding, existing_finding: Finding, - ) -> Tuple[Finding, bool]: + ) -> tuple[Finding, bool]: """ Determine how to handle the an existing finding based on the status that is possesses at the time of reimport @@ -453,7 +452,7 @@ def process_matched_special_status_finding( self, unsaved_finding: Finding, existing_finding: Finding, - ) -> Tuple[Finding, bool]: + ) -> tuple[Finding, bool]: """ Determine if there is parity between statuses of the new and existing finding. If so, do not touch either finding, and move on to the next unsaved finding @@ -488,7 +487,7 @@ def process_matched_mitigated_finding( self, unsaved_finding: Finding, existing_finding: Finding, - ) -> Tuple[Finding, bool]: + ) -> tuple[Finding, bool]: """ Determine how mitigated the existing and new findings really are. We need to cover circumstances where mitigation timestamps are different, and @@ -583,7 +582,7 @@ def process_matched_active_finding( self, unsaved_finding: Finding, existing_finding: Finding, - ) -> Tuple[Finding, bool]: + ) -> tuple[Finding, bool]: """ The existing finding must be active here, so we need to compare it closely with the new finding coming in and determine how to proceed @@ -734,7 +733,7 @@ def process_groups_for_all_findings( def process_results( self, **kwargs: dict, - ) -> Tuple[List[Finding], List[Finding], List[Finding], List[Finding]]: + ) -> tuple[list[Finding], list[Finding], list[Finding], list[Finding]]: """ Determine how to to return the results based on whether the process was ran asynchronous or not diff --git a/dojo/importers/endpoint_manager.py b/dojo/importers/endpoint_manager.py index 6686584da3c..625e3cb8073 100644 --- a/dojo/importers/endpoint_manager.py +++ b/dojo/importers/endpoint_manager.py @@ -1,5 +1,4 @@ import logging -from typing import List from django.conf import settings from django.core.exceptions import MultipleObjectsReturned, ValidationError @@ -25,7 +24,7 @@ class EndpointManager: def add_endpoints_to_unsaved_finding( self, finding: Finding, - endpoints: List[Endpoint], + endpoints: list[Endpoint], **kwargs: dict, ) -> None: """Creates Endpoint objects for a single finding and creates the link via the endpoint status""" @@ -61,7 +60,7 @@ def add_endpoints_to_unsaved_finding( @app.task() def mitigate_endpoint_status( self, - endpoint_status_list: List[Endpoint_Status], + endpoint_status_list: list[Endpoint_Status], user: Dojo_User, **kwargs: dict, ) -> None: @@ -81,7 +80,7 @@ def mitigate_endpoint_status( @app.task() def reactivate_endpoint_status( self, - endpoint_status_list: List[Endpoint_Status], + endpoint_status_list: list[Endpoint_Status], **kwargs: dict, ) -> None: """Reactivate all endpoint status objects that are supplied""" @@ -98,9 +97,9 @@ def reactivate_endpoint_status( def chunk_endpoints( self, - endpoint_list: List[Endpoint], + endpoint_list: list[Endpoint], chunk_size: int = settings.ASYNC_FINDING_IMPORT_CHUNK_SIZE, - ) -> List[List[Endpoint]]: + ) -> list[list[Endpoint]]: """ Split a single large list into a list of lists of size `chunk_size`. For Example @@ -117,7 +116,7 @@ def chunk_endpoints( def chunk_endpoints_and_disperse( self, finding: Finding, - endpoints: List[Endpoint], + endpoints: list[Endpoint], **kwargs: dict, ) -> None: """ @@ -141,7 +140,7 @@ def chunk_endpoints_and_disperse( def clean_unsaved_endpoints( self, - endpoints: List[Endpoint], + endpoints: list[Endpoint], ) -> None: """ Clean endpoints that are supplied. For any endpoints that fail this validation @@ -156,7 +155,7 @@ def clean_unsaved_endpoints( def chunk_endpoints_and_reactivate( self, - endpoint_status_list: List[Endpoint_Status], + endpoint_status_list: list[Endpoint_Status], **kwargs: dict, ) -> None: """ @@ -180,7 +179,7 @@ def chunk_endpoints_and_reactivate( def chunk_endpoints_and_mitigate( self, - endpoint_status_list: List[Endpoint_Status], + endpoint_status_list: list[Endpoint_Status], user: Dojo_User, **kwargs: dict, ) -> None: diff --git a/dojo/importers/options.py b/dojo/importers/options.py index 2431975856e..f458f2a4f36 100644 --- a/dojo/importers/options.py +++ b/dojo/importers/options.py @@ -1,8 +1,9 @@ import logging +from collections.abc import Callable from datetime import datetime from functools import wraps from pprint import pformat as pp -from typing import Any, Callable, List, Optional +from typing import Any from django.contrib.auth.models import User from django.db.models import Model @@ -57,19 +58,19 @@ def load_base_options( self.do_not_reactivate: bool = self.validate_do_not_reactivate(*args, **kwargs) self.commit_hash: str = self.validate_commit_hash(*args, **kwargs) self.create_finding_groups_for_all_findings: bool = self.validate_create_finding_groups_for_all_findings(*args, **kwargs) - self.endpoints_to_add: List[Endpoint] | None = self.validate_endpoints_to_add(*args, **kwargs) + self.endpoints_to_add: list[Endpoint] | None = self.validate_endpoints_to_add(*args, **kwargs) self.engagement: Engagement | None = self.validate_engagement(*args, **kwargs) self.environment: Development_Environment | None = self.validate_environment(*args, **kwargs) self.group_by: str = self.validate_group_by(*args, **kwargs) self.import_type: str = self.validate_import_type(*args, **kwargs) self.lead: Dojo_User | None = self.validate_lead(*args, **kwargs) self.minimum_severity: str = self.validate_minimum_severity(*args, **kwargs) - self.parsed_findings: List[Finding] | None = self.validate_parsed_findings(*args, **kwargs) + self.parsed_findings: list[Finding] | None = self.validate_parsed_findings(*args, **kwargs) self.push_to_jira: bool = self.validate_push_to_jira(*args, **kwargs) self.scan_date: datetime = self.validate_scan_date(*args, **kwargs) self.scan_type: str = self.validate_scan_type(*args, **kwargs) self.service: str = self.validate_service(*args, **kwargs) - self.tags: List[str] = self.validate_tags(*args, **kwargs) + self.tags: list[str] = self.validate_tags(*args, **kwargs) self.test: Test | None = self.validate_test(*args, **kwargs) self.user: Dojo_User | None = self.validate_user(*args, **kwargs) self.test_title: str = self.validate_test_title(*args, **kwargs) @@ -88,7 +89,7 @@ def load_additional_options( def log_translation( self, - header_message: Optional[str] = None, + header_message: str | None = None, ): if header_message is not None: logger.debug(header_message) @@ -181,7 +182,7 @@ def set_dict_fields( def validate( self, field_name: str, - expected_types: List[Callable] = [], + expected_types: list[Callable] = [], *, required: bool = False, default: Any = None, diff --git a/dojo/metrics/utils.py b/dojo/metrics/utils.py index 191c454b6e9..a6b947d2b97 100644 --- a/dojo/metrics/utils.py +++ b/dojo/metrics/utils.py @@ -1,9 +1,10 @@ import operator +from collections.abc import Callable from datetime import date, datetime, timedelta from enum import Enum from functools import partial -from typing import Any, Callable, NamedTuple, Type, TypeVar, Union +from typing import Any, NamedTuple, TypeVar from dateutil.relativedelta import relativedelta from django.contrib import messages @@ -34,7 +35,7 @@ ) -def get_metrics_finding_filter_class() -> Type[Union[MetricsFindingFilter, MetricsFindingFilterWithoutObjectLookups]]: +def get_metrics_finding_filter_class() -> type[MetricsFindingFilter | MetricsFindingFilterWithoutObjectLookups]: if get_system_setting("filter_string_matching", False): return MetricsFindingFilterWithoutObjectLookups return MetricsFindingFilter @@ -257,7 +258,7 @@ class _MetricsPeriodEntry(NamedTuple): """ datetime_name: str - db_method: Union[TruncWeek, TruncMonth] + db_method: TruncWeek | TruncMonth class MetricsPeriod(_MetricsPeriodEntry, Enum): @@ -346,7 +347,7 @@ def severity_count( queryset: MetricsQuerySet, method: str, expression: str, -) -> Union[MetricsQuerySet, dict[str, int]]: +) -> MetricsQuerySet | dict[str, int]: """ Aggregates counts by severity for the given queryset. @@ -393,7 +394,7 @@ def identify_view( def js_epoch( - d: Union[date, datetime], + d: date | datetime, ) -> int: """ Converts a date/datetime object to a JavaScript epoch time (for use in FE charts) diff --git a/dojo/models.py b/dojo/models.py index b34691b7103..aec1549d49d 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -6,7 +6,6 @@ import re import warnings from datetime import datetime -from typing import Dict, Optional, Set from uuid import uuid4 import hyperlink @@ -3409,7 +3408,7 @@ def severity(self): @cached_property def components(self): - components: Dict[str, Set[Optional[str]]] = {} + components: dict[str, set[str | None]] = {} for finding in self.findings.all(): if finding.component_name is not None: components.setdefault(finding.component_name, set()).add(finding.component_version) diff --git a/dojo/remote_user.py b/dojo/remote_user.py index 764af4e548b..a60fe52c899 100644 --- a/dojo/remote_user.py +++ b/dojo/remote_user.py @@ -100,8 +100,7 @@ def get_security_definition(self, auto_schema): return {} header_name = settings.AUTH_REMOTEUSER_USERNAME_HEADER - if header_name.startswith("HTTP_"): - header_name = header_name[5:] + header_name = header_name.removeprefix("HTTP_") header_name = header_name.replace("_", "-").capitalize() return { diff --git a/dojo/reports/views.py b/dojo/reports/views.py index aacf4369333..b9505ada877 100644 --- a/dojo/reports/views.py +++ b/dojo/reports/views.py @@ -3,7 +3,6 @@ import re from datetime import datetime from tempfile import NamedTemporaryFile -from typing import List from dateutil.relativedelta import relativedelta from django.conf import settings @@ -94,7 +93,7 @@ def get_endpoints(self, request: HttpRequest): filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter return filter_class(request.GET, queryset=endpoints, user=request.user) - def get_available_widgets(self, request: HttpRequest) -> List[Widget]: + def get_available_widgets(self, request: HttpRequest) -> list[Widget]: return [ CoverPage(request=request), TableOfContents(request=request), @@ -649,8 +648,7 @@ def get_findings(request): if not url: msg = "Please use the report button when viewing findings" raise Http404(msg) - if url.startswith("url="): - url = url[4:] + url = url.removeprefix("url=") views = ["all", "open", "inactive", "verified", "closed", "accepted", "out_of_scope", @@ -871,8 +869,7 @@ def get(self, request): for endpoint in finding.endpoints.all(): num_endpoints += 1 endpoint_value += f"{str(endpoint)}; " - if endpoint_value.endswith("; "): - endpoint_value = endpoint_value[:-2] + endpoint_value = endpoint_value.removesuffix("; ") if len(endpoint_value) > EXCEL_CHAR_LIMIT: endpoint_value = endpoint_value[:EXCEL_CHAR_LIMIT - 3] + "..." fields.append(endpoint_value) @@ -887,8 +884,7 @@ def get(self, request): vulnerability_ids_value += f"{str(vulnerability_id)}; " if finding.cve and vulnerability_ids_value.find(finding.cve) < 0: vulnerability_ids_value += finding.cve - if vulnerability_ids_value.endswith("; "): - vulnerability_ids_value = vulnerability_ids_value[:-2] + vulnerability_ids_value = vulnerability_ids_value.removesuffix("; ") fields.append(vulnerability_ids_value) # Tags tags_value = "" @@ -899,8 +895,7 @@ def get(self, request): tags_value += "..." break tags_value += f"{str(tag)}; " - if tags_value.endswith("; "): - tags_value = tags_value[:-2] + tags_value = tags_value.removesuffix("; ") fields.append(tags_value) self.fields = fields @@ -1021,8 +1016,7 @@ def get(self, request): for endpoint in finding.endpoints.all(): num_endpoints += 1 endpoint_value += f"{str(endpoint)}; \n" - if endpoint_value.endswith("; \n"): - endpoint_value = endpoint_value[:-3] + endpoint_value = endpoint_value.removesuffix("; \n") if len(endpoint_value) > EXCEL_CHAR_LIMIT: endpoint_value = endpoint_value[:EXCEL_CHAR_LIMIT - 3] + "..." worksheet.cell(row=row_num, column=col_num, value=endpoint_value) @@ -1038,16 +1032,14 @@ def get(self, request): vulnerability_ids_value += f"{str(vulnerability_id)}; \n" if finding.cve and vulnerability_ids_value.find(finding.cve) < 0: vulnerability_ids_value += finding.cve - if vulnerability_ids_value.endswith("; \n"): - vulnerability_ids_value = vulnerability_ids_value[:-3] + vulnerability_ids_value = vulnerability_ids_value.removesuffix("; \n") worksheet.cell(row=row_num, column=col_num, value=vulnerability_ids_value) col_num += 1 # tags tags_value = "" for tag in finding.tags.all(): tags_value += f"{str(tag)}; \n" - if tags_value.endswith("; \n"): - tags_value = tags_value[:-3] + tags_value = tags_value.removesuffix("; \n") worksheet.cell(row=row_num, column=col_num, value=tags_value) col_num += 1 self.col_num = col_num diff --git a/dojo/risk_acceptance/api.py b/dojo/risk_acceptance/api.py index 4fc89a32fe0..2fdaadf0afb 100644 --- a/dojo/risk_acceptance/api.py +++ b/dojo/risk_acceptance/api.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import List, NamedTuple +from typing import NamedTuple from django.db.models import QuerySet from django.utils import timezone @@ -81,7 +81,7 @@ def accept_risks(self, request): return Response(status=201, data=result.data) -def _accept_risks(accepted_risks: List[AcceptedRisk], base_findings: QuerySet, owner: User): +def _accept_risks(accepted_risks: list[AcceptedRisk], base_findings: QuerySet, owner: User): accepted = [] for risk in accepted_risks: vulnerability_ids = Vulnerability_Id.objects \ diff --git a/dojo/system_settings/views.py b/dojo/system_settings/views.py index 4c952d57a0f..584fa547d32 100644 --- a/dojo/system_settings/views.py +++ b/dojo/system_settings/views.py @@ -1,5 +1,4 @@ import logging -from typing import Tuple from django.conf import settings from django.contrib import messages @@ -59,7 +58,7 @@ def validate_form( self, request: HttpRequest, context: dict, - ) -> Tuple[HttpRequest, bool]: + ) -> tuple[HttpRequest, bool]: if context["form"].is_valid(): if (context["form"].cleaned_data["default_group"] is None and context["form"].cleaned_data["default_group_role"] is not None) or \ (context["form"].cleaned_data["default_group"] is not None and context["form"].cleaned_data["default_group_role"] is None): diff --git a/dojo/test/views.py b/dojo/test/views.py index 96d3a58c1ed..76b0bcd2aa5 100644 --- a/dojo/test/views.py +++ b/dojo/test/views.py @@ -4,7 +4,6 @@ import operator from datetime import datetime from functools import reduce -from typing import Tuple from django.contrib import messages from django.contrib.admin.utils import NestedObjects @@ -826,7 +825,7 @@ def get_jira_form( self, request: HttpRequest, test: Test, - ) -> Tuple[JIRAImportScanForm | None, bool]: + ) -> tuple[JIRAImportScanForm | None, bool]: """Returns a JiraImportScanForm if jira is enabled""" jira_form = None push_all_jira_issues = False @@ -853,7 +852,7 @@ def handle_request( self, request: HttpRequest, test_id: int, - ) -> Tuple[HttpRequest, dict]: + ) -> tuple[HttpRequest, dict]: """ Process the common behaviors between request types, and then return the request and context dict back to be rendered diff --git a/dojo/tools/appcheck_web_application_scanner/engines/appcheck.py b/dojo/tools/appcheck_web_application_scanner/engines/appcheck.py index 6d10485e4d1..b34931d0f8f 100644 --- a/dojo/tools/appcheck_web_application_scanner/engines/appcheck.py +++ b/dojo/tools/appcheck_web_application_scanner/engines/appcheck.py @@ -1,5 +1,4 @@ import re -from typing import Union from dojo.models import Finding from dojo.tools.appcheck_web_application_scanner.engines.base import BaseEngineParser @@ -29,7 +28,7 @@ def extract_request_response(self, finding: Finding, value: dict[str, [str]]) -> value.pop("Messages") finding.unsaved_request, finding.unsaved_response = (d.strip() for d in rr_details[0]) - def parse_details(self, finding: Finding, value: dict[str, Union[str, dict[str, list[str]]]]) -> None: + def parse_details(self, finding: Finding, value: dict[str, str | dict[str, list[str]]]) -> None: self.extract_request_response(finding, value) # super's version adds everything else to the description field return super().parse_details(finding, value) diff --git a/dojo/tools/appcheck_web_application_scanner/engines/base.py b/dojo/tools/appcheck_web_application_scanner/engines/base.py index 782c047443a..e07433c2946 100644 --- a/dojo/tools/appcheck_web_application_scanner/engines/base.py +++ b/dojo/tools/appcheck_web_application_scanner/engines/base.py @@ -1,6 +1,6 @@ import re from itertools import starmap -from typing import Any, Optional, Tuple, Union +from typing import Any import cvss.parser import dateutil.parser @@ -193,7 +193,7 @@ def __init__(self): ##### # For parsing the initial finding datetime to a date format pleasing to Finding ##### - def get_date(self, value: str) -> Optional[str]: + def get_date(self, value: str) -> str | None: try: return str(dateutil.parser.parse(value).date()) except dateutil.parser.ParserError: @@ -229,7 +229,7 @@ def parse_status(self, finding: Finding, value: str) -> None: ##### # For parsing component data ##### - def parse_cpe(self, cpe_str: str) -> (Optional[str], Optional[str]): + def parse_cpe(self, cpe_str: str) -> (str | None, str | None): if not cpe_str: return None, None cpe_obj = CPE(cpe_str) @@ -257,12 +257,12 @@ def append_description(self, finding: Finding, addendum: dict[str, str]) -> None def parse_notes(self, finding: Finding, value: str) -> None: self.append_description(finding, {"Notes": value}) - def extract_details(self, value: Union[str, dict[str, Union[str, dict[str, list[str]]]]]) -> dict[str, str]: + def extract_details(self, value: str | dict[str, str | dict[str, list[str]]]) -> dict[str, str]: if isinstance(value, dict): return {k: v for k, v in value.items() if k != "_meta"} return {"Details": str(value)} - def parse_details(self, finding: Finding, value: dict[str, Union[str, dict[str, list[str]]]]) -> None: + def parse_details(self, finding: Finding, value: dict[str, str | dict[str, list[str]]]) -> None: self.append_description(finding, self.extract_details(value)) ##### @@ -271,7 +271,7 @@ def parse_details(self, finding: Finding, value: dict[str, Union[str, dict[str, def get_host(self, item: dict[str, Any]) -> str: return item.get("url") or item.get("host") or item.get("ipv4_address") or None - def parse_port(self, item: Any) -> Optional[int]: + def parse_port(self, item: Any) -> int | None: try: int_val = int(item) if 0 < int_val <= 65535: @@ -280,10 +280,10 @@ def parse_port(self, item: Any) -> Optional[int]: pass return None - def get_port(self, item: dict[str, Any]) -> Optional[int]: + def get_port(self, item: dict[str, Any]) -> int | None: return self.parse_port(item.get("port")) - def construct_endpoint(self, host: str, port: Optional[int]) -> Endpoint: + def construct_endpoint(self, host: str, port: int | None) -> Endpoint: endpoint = Endpoint.from_uri(host) if endpoint.host: if port: @@ -306,7 +306,7 @@ def set_endpoints(self, finding: Finding, item: Any) -> None: ##### # For severity (extracted from various cvss vectors) ##### - def parse_cvss_vector(self, value: str) -> Optional[str]: + def parse_cvss_vector(self, value: str) -> str | None: # CVSS4 vectors don't parse with the handy-danty parse method :( try: if (severity := cvss.CVSS4(value).severity) in Finding.SEVERITIES: @@ -347,7 +347,7 @@ def get_engine_fields(self) -> dict[str, FieldType]: **BaseEngineParser._COMMON_FIELDS_MAP, **self._ENGINE_FIELDS_MAP} - def get_finding_key(self, finding: Finding) -> Tuple: + def get_finding_key(self, finding: Finding) -> tuple: return ( finding.severity, finding.title, @@ -355,7 +355,7 @@ def get_finding_key(self, finding: Finding) -> Tuple: self.SCANNING_ENGINE, ) - def parse_finding(self, item: dict[str, Any]) -> Tuple[Finding, Tuple]: + def parse_finding(self, item: dict[str, Any]) -> tuple[Finding, tuple]: finding = Finding() for field, field_handler in self.get_engine_fields().items(): # Check first whether the field even exists on this item entry; if not, skip it diff --git a/dojo/tools/appcheck_web_application_scanner/engines/nmap.py b/dojo/tools/appcheck_web_application_scanner/engines/nmap.py index 9252bdcb53d..3fba10e455d 100644 --- a/dojo/tools/appcheck_web_application_scanner/engines/nmap.py +++ b/dojo/tools/appcheck_web_application_scanner/engines/nmap.py @@ -1,4 +1,4 @@ -from typing import Any, Union +from typing import Any from dojo.models import Endpoint from dojo.tools.appcheck_web_application_scanner.engines.base import BaseEngineParser @@ -18,7 +18,7 @@ class NmapScanningEngineParser(BaseEngineParser): def is_port_table_entry(self, entry) -> bool: return len(entry) > 0 and self.parse_port(entry[0]) - def get_ports(self, item) -> Union[list[int], list[None]]: + def get_ports(self, item) -> list[int] | list[None]: meta = item.get("meta") if not isinstance(meta, dict): meta = {} diff --git a/dojo/tools/blackduck/importer.py b/dojo/tools/blackduck/importer.py index 83175371705..51caa180765 100644 --- a/dojo/tools/blackduck/importer.py +++ b/dojo/tools/blackduck/importer.py @@ -4,8 +4,8 @@ import zipfile from abc import ABC, abstractmethod from collections import defaultdict +from collections.abc import Iterable from pathlib import Path -from typing import Iterable from .model import BlackduckFinding diff --git a/dojo/tools/blackduck_binary_analysis/importer.py b/dojo/tools/blackduck_binary_analysis/importer.py index 4d381aae058..0ada8cca26b 100644 --- a/dojo/tools/blackduck_binary_analysis/importer.py +++ b/dojo/tools/blackduck_binary_analysis/importer.py @@ -1,8 +1,8 @@ import csv from abc import ABC, abstractmethod from collections import defaultdict +from collections.abc import Iterable from pathlib import Path -from typing import Iterable from .model import BlackduckBinaryAnalysisFinding diff --git a/dojo/tools/checkmarx_one/parser.py b/dojo/tools/checkmarx_one/parser.py index a48023e5d6f..f8896c0b271 100644 --- a/dojo/tools/checkmarx_one/parser.py +++ b/dojo/tools/checkmarx_one/parser.py @@ -1,7 +1,6 @@ import datetime import json import re -from typing import List from dateutil import parser from django.conf import settings @@ -40,7 +39,7 @@ def parse_vulnerabilities_from_scan_list( self, test: Test, data: dict, - ) -> List[Finding]: + ) -> list[Finding]: findings = [] cwe_store = data.get("vulnerabilityDetails", []) # SAST @@ -59,7 +58,7 @@ def parse_iac_vulnerabilities( test: Test, results: list, cwe_store: list, - ) -> List[Finding]: + ) -> list[Finding]: findings = [] for technology in results: # Set the name aside for use in the title @@ -109,7 +108,7 @@ def parse_sca_vulnerabilities( test: Test, results: list, cwe_store: list, - ) -> List[Finding]: + ) -> list[Finding]: # Not implemented yet return [] @@ -118,7 +117,7 @@ def parse_sast_vulnerabilities( test: Test, results: list, cwe_store: list, - ) -> List[Finding]: + ) -> list[Finding]: def get_cwe_store_entry(cwe_store: list, cwe: int) -> dict: # Quick base case if cwe is None: @@ -197,7 +196,7 @@ def parse_vulnerabilities( self, test: Test, results: list, - ) -> List[Finding]: + ) -> list[Finding]: findings = [] for result in results: id = result.get("identifiers")[0].get("value") @@ -233,7 +232,7 @@ def parse_results( self, test: Test, results: list, - ) -> List[Finding]: + ) -> list[Finding]: findings = [] for vulnerability in results: result_type = vulnerability.get("type") diff --git a/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py b/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py index 2fa4dd3c290..7516cfe211f 100644 --- a/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py +++ b/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py @@ -136,8 +136,7 @@ def get_vuln_id_from_tool(vulnerability): def clean_title(title): - if title.startswith("Issue summary: "): - title = title[len("Issue summary: "):] + title = title.removeprefix("Issue summary: ") if "\n" in title: title = title[:title.index("\n")] return title diff --git a/dojo/tools/kics/parser.py b/dojo/tools/kics/parser.py index 5eb30227b5a..31645ab7da3 100644 --- a/dojo/tools/kics/parser.py +++ b/dojo/tools/kics/parser.py @@ -53,8 +53,7 @@ def get_findings(self, filename, test): description += f"**Issue type:** {issue_type}\n" if actual_value: description += f"**Actual value:** {actual_value}\n" - if description.endswith("\n"): - description = description[:-1] + description = description.removesuffix("\n") dupe_key = hashlib.sha256( ( diff --git a/dojo/tools/sarif/parser.py b/dojo/tools/sarif/parser.py index e6c68388841..ae9736e9a0f 100644 --- a/dojo/tools/sarif/parser.py +++ b/dojo/tools/sarif/parser.py @@ -297,10 +297,7 @@ def get_description(result, rule): if len(result.get("codeFlows", [])) > 0: description += get_codeFlowsDescription(result["codeFlows"]) - if description.endswith("\n"): - description = description[:-1] - - return description + return description.removesuffix("\n") def get_references(rule): diff --git a/dojo/tools/tenable/xml_format.py b/dojo/tools/tenable/xml_format.py index 7094e82d626..438ce2220e5 100644 --- a/dojo/tools/tenable/xml_format.py +++ b/dojo/tools/tenable/xml_format.py @@ -51,7 +51,7 @@ def safely_get_element_text(self, element): return None if isinstance(element_text, str): return element_text if len(element_text) > 0 else None - if isinstance(element_text, (int, float)): + if isinstance(element_text, int | float): return element_text or None return None diff --git a/dojo/tools/veracode/json_parser.py b/dojo/tools/veracode/json_parser.py index 6584d213821..55ea07602d3 100644 --- a/dojo/tools/veracode/json_parser.py +++ b/dojo/tools/veracode/json_parser.py @@ -130,7 +130,7 @@ def create_finding_from_details(self, finding_details, scan_type, policy_violate finding.cvssv3 = CVSS3(str(uncleaned_cvss)).clean_vector(output_prefix=True) elif not uncleaned_cvss.startswith("CVSS"): finding.cvssv3 = CVSS3(f"CVSS:3.1/{str(uncleaned_cvss)}").clean_vector(output_prefix=True) - elif isinstance(uncleaned_cvss, (float, int)): + elif isinstance(uncleaned_cvss, float | int): finding.cvssv3_score = float(uncleaned_cvss) # Fill in extra info based on the scan type if scan_type == "STATIC": diff --git a/dojo/tools/whitehat_sentinel/parser.py b/dojo/tools/whitehat_sentinel/parser.py index 325bd364283..c23d002cb84 100644 --- a/dojo/tools/whitehat_sentinel/parser.py +++ b/dojo/tools/whitehat_sentinel/parser.py @@ -3,7 +3,6 @@ import logging import re from datetime import datetime -from typing import List, Union from dojo.models import Endpoint, Finding @@ -55,7 +54,7 @@ def get_findings(self, file, test): def _convert_whitehat_severity_id_to_dojo_severity( self, whitehat_severity_id: int, - ) -> Union[str, None]: + ) -> str | None: """ Converts a WhiteHat Sentinel numerical severity to a DefectDojo severity. Args: @@ -165,8 +164,8 @@ def __remove_paragraph_tags(self, html_string): return re.sub(r"

|

", "", html_string) def _convert_attack_vectors_to_endpoints( - self, attack_vectors: List[dict], - ) -> List["Endpoint"]: + self, attack_vectors: list[dict], + ) -> list["Endpoint"]: """ Takes a list of Attack Vectors dictionaries from the WhiteHat vuln API and converts them to Defect Dojo Endpoints diff --git a/dojo/utils.py b/dojo/utils.py index 470d8607725..35ccac5aea2 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -7,9 +7,9 @@ import os import re from calendar import monthrange +from collections.abc import Callable from datetime import date, datetime, timedelta from math import pi, sqrt -from typing import Callable, Optional import bleach import crum @@ -2586,7 +2586,7 @@ def get_open_findings_burndown(product): return past_90_days -def get_custom_method(setting_name: str) -> Optional[Callable]: +def get_custom_method(setting_name: str) -> Callable | None: """ Attempts to load and return the method specified by fully-qualified name at the given setting. diff --git a/ruff.toml b/ruff.toml index 376a6d3cc7b..9a34bf6e005 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,3 +1,6 @@ +# Always generate Python 3.11-compatible code. +target-version = "py311" + # Same as Black. line-length = 120 diff --git a/unittests/test_bulk_risk_acceptance_api.py b/unittests/test_bulk_risk_acceptance_api.py index bdc87451d04..05bbe10e7a8 100644 --- a/unittests/test_bulk_risk_acceptance_api.py +++ b/unittests/test_bulk_risk_acceptance_api.py @@ -29,25 +29,25 @@ def setUpTestData(cls): cls.product = Product.objects.create(prod_type=cls.product_type, name="Flopper", description="Test product") Product_Type_Member.objects.create(product_type=cls.product_type, user=cls.user, role=Role.objects.get(id=Roles.Owner)) cls.product_2 = Product.objects.create(prod_type=cls.product_type, name="Flopper2", description="Test product2") - cls.engagement = Engagement.objects.create(product=cls.product, target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), - target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.timezone.utc)) - cls.engagement_2a = Engagement.objects.create(product=cls.product_2, target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), - target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.timezone.utc)) - cls.engagement_2b = Engagement.objects.create(product=cls.product_2, target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), - target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.timezone.utc)) + cls.engagement = Engagement.objects.create(product=cls.product, target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC), + target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.UTC)) + cls.engagement_2a = Engagement.objects.create(product=cls.product_2, target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC), + target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.UTC)) + cls.engagement_2b = Engagement.objects.create(product=cls.product_2, target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC), + target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.UTC)) cls.test_type = Test_Type.objects.create(name="Risk Acceptance Mock Scan", static_tool=True) cls.test_a = Test.objects.create(engagement=cls.engagement, test_type=cls.test_type, - target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.timezone.utc)) + target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.UTC)) cls.test_b = Test.objects.create(engagement=cls.engagement, test_type=cls.test_type, - target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.timezone.utc)) + target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.UTC)) cls.test_c = Test.objects.create(engagement=cls.engagement, test_type=cls.test_type, - target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.timezone.utc)) + target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.UTC)) cls.test_d = Test.objects.create(engagement=cls.engagement_2a, test_type=cls.test_type, - target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.timezone.utc)) + target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.UTC)) cls.test_e = Test.objects.create(engagement=cls.engagement_2b, test_type=cls.test_type, - target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.timezone.utc)) + target_start=datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC), target_end=datetime.datetime(2000, 2, 1, tzinfo=datetime.UTC)) def create_finding(test: Test, reporter: User, cve: str) -> Finding: return Finding(test=test, title=f"Finding {cve}", cve=cve, severity="High", verified=True, diff --git a/unittests/test_dashboard.py b/unittests/test_dashboard.py index 81d9000e40a..35e3eabbde6 100644 --- a/unittests/test_dashboard.py +++ b/unittests/test_dashboard.py @@ -1,5 +1,4 @@ from datetime import datetime, timedelta -from typing import List, Tuple from unittest.mock import patch from dateutil.relativedelta import relativedelta @@ -14,7 +13,7 @@ User = get_user_model() -def create(when: datetime, product_id: int, titles_and_severities: List[Tuple[str, str]]): +def create(when: datetime, product_id: int, titles_and_severities: list[tuple[str, str]]): with patch("django.db.models.fields.timezone.now") as mock_now: mock_now.return_value = when engagement = Engagement.objects.create(product_id=product_id, target_start=when.date(), target_end=when.date()) @@ -25,7 +24,7 @@ def create(when: datetime, product_id: int, titles_and_severities: List[Tuple[st ) -def create_with_duplicates(when: datetime, product_id: int, titles_and_severities: List[Tuple[str, str]]): +def create_with_duplicates(when: datetime, product_id: int, titles_and_severities: list[tuple[str, str]]): with patch("django.db.models.fields.timezone.now") as mock_now: mock_now.return_value = when engagement = Engagement.objects.create(product_id=product_id, target_start=when.date(), target_end=when.date()) diff --git a/unittests/test_finding_helper.py b/unittests/test_finding_helper.py index 7ff00889c09..8d3432864d9 100644 --- a/unittests/test_finding_helper.py +++ b/unittests/test_finding_helper.py @@ -96,7 +96,7 @@ def test_mark_old_active_as_mitigated(self, mock_can_edit, mock_tz): def test_mark_old_active_as_mitigated_custom_edit(self, mock_can_edit, mock_tz): mock_tz.return_value = frozen_datetime - custom_mitigated = datetime.datetime.now(datetime.timezone.utc) + custom_mitigated = datetime.datetime.now(datetime.UTC) with impersonate(self.user_1): test = Test.objects.last() @@ -118,7 +118,7 @@ def test_mark_old_active_as_mitigated_custom_edit(self, mock_can_edit, mock_tz): def test_update_old_mitigated_with_custom_edit(self, mock_can_edit, mock_tz): mock_tz.return_value = frozen_datetime - custom_mitigated = datetime.datetime.now(datetime.timezone.utc) + custom_mitigated = datetime.datetime.now(datetime.UTC) with impersonate(self.user_1): test = Test.objects.last() @@ -140,7 +140,7 @@ def test_update_old_mitigated_with_custom_edit(self, mock_can_edit, mock_tz): def test_update_old_mitigated_with_missing_data(self, mock_can_edit, mock_tz): mock_tz.return_value = frozen_datetime - custom_mitigated = datetime.datetime.now(datetime.timezone.utc) + custom_mitigated = datetime.datetime.now(datetime.UTC) with impersonate(self.user_1): test = Test.objects.last() diff --git a/unittests/test_flush_auditlog.py b/unittests/test_flush_auditlog.py index a75473664be..1c7f5ef08df 100644 --- a/unittests/test_flush_auditlog.py +++ b/unittests/test_flush_auditlog.py @@ -1,5 +1,5 @@ import logging -from datetime import date, datetime, timezone +from datetime import UTC, date, datetime from auditlog.models import LogEntry from dateutil.relativedelta import relativedelta @@ -33,8 +33,8 @@ def test_delete_all_entries(self): @override_settings(AUDITLOG_FLUSH_RETENTION_PERIOD=1) def test_delete_entries_with_retention_period(self): - entries_before = LogEntry.objects.filter(timestamp__date__lt=datetime.now(timezone.utc)).count() - two_weeks_ago = datetime.now(timezone.utc) - relativedelta(weeks=2) + entries_before = LogEntry.objects.filter(timestamp__date__lt=datetime.now(UTC)).count() + two_weeks_ago = datetime.now(UTC) - relativedelta(weeks=2) log_entry = LogEntry.objects.log_create( instance=Finding.objects.all()[0], timestamp=two_weeks_ago, @@ -44,6 +44,6 @@ def test_delete_entries_with_retention_period(self): log_entry.timestamp = two_weeks_ago log_entry.save() flush_auditlog() - entries_after = LogEntry.objects.filter(timestamp__date__lt=datetime.now(timezone.utc)).count() + entries_after = LogEntry.objects.filter(timestamp__date__lt=datetime.now(UTC)).count() # we have three old log entries in our testdata and added a new one self.assertEqual(entries_before - 3 + 1, entries_after) diff --git a/unittests/test_import_reimport.py b/unittests/test_import_reimport.py index 03cea9b0b0d..b0f7da906b6 100644 --- a/unittests/test_import_reimport.py +++ b/unittests/test_import_reimport.py @@ -1456,8 +1456,8 @@ def test_import_reimport_vulnerability_ids(self): engagement=test.engagement, test_type=test_type, scan_type=self.anchore_grype_scan_type, - target_start=datetime.datetime.now(datetime.timezone.utc), - target_end=datetime.datetime.now(datetime.timezone.utc), + target_start=datetime.datetime.now(datetime.UTC), + target_end=datetime.datetime.now(datetime.UTC), ) reimport_test.save() diff --git a/unittests/test_metrics_queries.py b/unittests/test_metrics_queries.py index 6bd54ff9e89..68e754b6d6d 100644 --- a/unittests/test_metrics_queries.py +++ b/unittests/test_metrics_queries.py @@ -1,6 +1,6 @@ """Tests for metrics database queries""" -from datetime import date, datetime, timezone +from datetime import UTC, date, datetime from unittest.mock import patch import pytz @@ -21,23 +21,23 @@ def add(*args, **kwargs): #### # Test Findings data #### -FINDING_1 = {"id": 4, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_2 = {"id": 5, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_3 = {"id": 6, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_4 = {"id": 7, "title": "DUMMY FINDING", "date": date(2017, 12, 31), "sla_start_date": None, "sla_expiration_date": None, "cwe": 1, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": "http://www.example.com", "severity": "High", "description": "TEST finding", "mitigation": "MITIGATION", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": False, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 2, "under_defect_review": False, "defect_review_requested_by_id": 2, "is_mitigated": False, "thread_id": 1, "mitigated": None, "mitigated_by_id": None, "reporter_id": 2, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "c89d25e445b088ba339908f68e15e3177b78d22f3039d1bfea51c4be251bf4e0", "line": 100, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_5 = {"id": 24, "title": "Low Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 33, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 22, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_6 = {"id": 125, "title": "Low Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 55, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "12345", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_7 = {"id": 225, "title": "UID Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 77, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 224, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "6f8d0bf970c14175e597843f4679769a4775742549d90f902ff803de9244c7e1", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "6789", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_8 = {"id": 240, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": True, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_9 = {"id": 241, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": True, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_10 = {"id": 242, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": True, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_11 = {"id": 243, "title": "DUMMY FINDING", "date": date(2017, 12, 31), "sla_start_date": None, "sla_expiration_date": None, "cwe": 1, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": "http://www.example.com", "severity": "High", "description": "TEST finding", "mitigation": "MITIGATION", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": False, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": True, "under_review": False, "last_status_update": None, "review_requested_by_id": 2, "under_defect_review": False, "defect_review_requested_by_id": 2, "is_mitigated": True, "thread_id": 1, "mitigated": None, "mitigated_by_id": None, "reporter_id": 2, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "c89d25e445b088ba339908f68e15e3177b78d22f3039d1bfea51c4be251bf4e0", "line": 100, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_12 = {"id": 244, "title": "Low Impact Test Finding", "date": date(2017, 12, 29), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 33, "active": True, "verified": True, "false_p": False, "duplicate": False, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_13 = {"id": 245, "title": "Low Impact Test Finding", "date": date(2017, 12, 27), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 33, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 22, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_14 = {"id": 246, "title": "Low Impact Test Finding", "date": date(2018, 1, 2), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 33, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 22, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_15 = {"id": 247, "title": "Low Impact Test Finding", "date": date(2018, 1, 3), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 55, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "12345", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_16 = {"id": 248, "title": "UID Impact Test Finding", "date": date(2017, 12, 27), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 77, "active": True, "verified": True, "false_p": False, "duplicate": False, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": True, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "6f8d0bf970c14175e597843f4679769a4775742549d90f902ff803de9244c7e1", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "6789", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} -FINDING_17 = {"id": 249, "title": "UID Impact Test Finding", "date": date(2018, 1, 4), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 77, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 224, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "6f8d0bf970c14175e597843f4679769a4775742549d90f902ff803de9244c7e1", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=timezone.utc), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "6789", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_1 = {"id": 4, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_2 = {"id": 5, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_3 = {"id": 6, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_4 = {"id": 7, "title": "DUMMY FINDING", "date": date(2017, 12, 31), "sla_start_date": None, "sla_expiration_date": None, "cwe": 1, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": "http://www.example.com", "severity": "High", "description": "TEST finding", "mitigation": "MITIGATION", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": False, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 2, "under_defect_review": False, "defect_review_requested_by_id": 2, "is_mitigated": False, "thread_id": 1, "mitigated": None, "mitigated_by_id": None, "reporter_id": 2, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "c89d25e445b088ba339908f68e15e3177b78d22f3039d1bfea51c4be251bf4e0", "line": 100, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_5 = {"id": 24, "title": "Low Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 33, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 22, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_6 = {"id": 125, "title": "Low Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 55, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "12345", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_7 = {"id": 225, "title": "UID Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 77, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 224, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "6f8d0bf970c14175e597843f4679769a4775742549d90f902ff803de9244c7e1", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "6789", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_8 = {"id": 240, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": True, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_9 = {"id": 241, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": True, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_10 = {"id": 242, "title": "High Impact Test Finding", "date": date(2018, 1, 1), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "High", "description": "test finding", "mitigation": "test mitigation", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 2, "out_of_scope": False, "risk_accepted": True, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "5d368a051fdec959e08315a32ef633ba5711bed6e8e75319ddee2cab4d4608c7", "line": None, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_11 = {"id": 243, "title": "DUMMY FINDING", "date": date(2017, 12, 31), "sla_start_date": None, "sla_expiration_date": None, "cwe": 1, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": "http://www.example.com", "severity": "High", "description": "TEST finding", "mitigation": "MITIGATION", "impact": "High", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 3, "active": False, "verified": False, "false_p": False, "duplicate": False, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": True, "under_review": False, "last_status_update": None, "review_requested_by_id": 2, "under_defect_review": False, "defect_review_requested_by_id": 2, "is_mitigated": True, "thread_id": 1, "mitigated": None, "mitigated_by_id": None, "reporter_id": 2, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "c89d25e445b088ba339908f68e15e3177b78d22f3039d1bfea51c4be251bf4e0", "line": 100, "file_path": "", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_12 = {"id": 244, "title": "Low Impact Test Finding", "date": date(2017, 12, 29), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 33, "active": True, "verified": True, "false_p": False, "duplicate": False, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_13 = {"id": 245, "title": "Low Impact Test Finding", "date": date(2017, 12, 27), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 33, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 22, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_14 = {"id": 246, "title": "Low Impact Test Finding", "date": date(2018, 1, 2), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 33, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 22, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": None, "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_15 = {"id": 247, "title": "Low Impact Test Finding", "date": date(2018, 1, 3), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 55, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "9aca00affd340c4da02c934e7e3106a45c6ad0911da479daae421b3b28a2c1aa", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "12345", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_16 = {"id": 248, "title": "UID Impact Test Finding", "date": date(2017, 12, 27), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 77, "active": True, "verified": True, "false_p": False, "duplicate": False, "duplicate_finding_id": None, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": True, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "6f8d0bf970c14175e597843f4679769a4775742549d90f902ff803de9244c7e1", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "6789", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} +FINDING_17 = {"id": 249, "title": "UID Impact Test Finding", "date": date(2018, 1, 4), "sla_start_date": None, "sla_expiration_date": None, "cwe": None, "cve": None, "epss_score": None, "epss_percentile": None, "cvssv3": None, "cvssv3_score": None, "url": None, "severity": "Low", "description": "test finding", "mitigation": "test mitigation", "impact": "Low", "steps_to_reproduce": None, "severity_justification": None, "references": "", "test_id": 77, "active": False, "verified": False, "false_p": False, "duplicate": True, "duplicate_finding_id": 224, "out_of_scope": False, "risk_accepted": False, "under_review": False, "last_status_update": None, "review_requested_by_id": 1, "under_defect_review": False, "defect_review_requested_by_id": 1, "is_mitigated": False, "thread_id": 11, "mitigated": None, "mitigated_by_id": None, "reporter_id": 1, "numerical_severity": "S0", "last_reviewed": None, "last_reviewed_by_id": None, "param": None, "payload": None, "hash_code": "6f8d0bf970c14175e597843f4679769a4775742549d90f902ff803de9244c7e1", "line": 123, "file_path": "/dev/urandom", "component_name": None, "component_version": None, "static_finding": False, "dynamic_finding": False, "created": datetime(2017, 12, 1, 0, 0, tzinfo=UTC), "scanner_confidence": None, "sonarqube_issue_id": None, "unique_id_from_tool": "6789", "vuln_id_from_tool": None, "sast_source_object": None, "sast_sink_object": None, "sast_source_line": None, "sast_source_file_path": None, "nb_occurences": None, "publish_date": None, "service": None, "planned_remediation_date": None, "planned_remediation_version": None, "effort_for_fixing": None, "test__engagement__product__prod_type__member": False, "test__engagement__product__member": True, "test__engagement__product__prod_type__authorized_group": False, "test__engagement__product__authorized_group": False} ALL_FINDINGS = [FINDING_1, FINDING_2, FINDING_3, FINDING_4, FINDING_5, FINDING_6, FINDING_7, FINDING_8, FINDING_9, @@ -75,7 +75,7 @@ def test_finding_queries_no_data(self): @patch("django.utils.timezone.now") def test_finding_queries(self, mock_timezone): - mock_datetime = datetime(2020, 12, 9, tzinfo=timezone.utc) + mock_datetime = datetime(2020, 12, 9, tzinfo=UTC) mock_timezone.return_value = mock_datetime # Queries over Finding @@ -280,5 +280,5 @@ def test_endpoint_queries(self): ], ) self.assertEqual(endpoint_queries["weeks_between"], 2) - self.assertEqual(endpoint_queries["start_date"], datetime(2020, 7, 1, 0, 0, tzinfo=timezone.utc)) - self.assertEqual(endpoint_queries["end_date"], datetime(2020, 7, 1, 0, 0, tzinfo=timezone.utc)) + self.assertEqual(endpoint_queries["start_date"], datetime(2020, 7, 1, 0, 0, tzinfo=UTC)) + self.assertEqual(endpoint_queries["end_date"], datetime(2020, 7, 1, 0, 0, tzinfo=UTC)) diff --git a/unittests/test_risk_acceptance.py b/unittests/test_risk_acceptance.py index 97afb3e1f7d..9e7904f4716 100644 --- a/unittests/test_risk_acceptance.py +++ b/unittests/test_risk_acceptance.py @@ -269,9 +269,9 @@ def test_expiration_handler(self): # ra1: expire in 9 days -> warn:yes, expire:no # ra2: expire in 11 days -> warn:no, expire:no # ra3: expire 5 days ago -> warn:no, expire:yes (expiration not handled yet, so expire) - ra1.expiration_date = datetime.datetime.now(datetime.timezone.utc) + relativedelta(days=heads_up_days - 1) - ra2.expiration_date = datetime.datetime.now(datetime.timezone.utc) + relativedelta(days=heads_up_days + 1) - ra3.expiration_date = datetime.datetime.now(datetime.timezone.utc) - relativedelta(days=5) + ra1.expiration_date = datetime.datetime.now(datetime.UTC) + relativedelta(days=heads_up_days - 1) + ra2.expiration_date = datetime.datetime.now(datetime.UTC) + relativedelta(days=heads_up_days + 1) + ra3.expiration_date = datetime.datetime.now(datetime.UTC) - relativedelta(days=5) ra1.save() ra2.save() ra3.save() diff --git a/unittests/test_utils_deduplication_reopen.py b/unittests/test_utils_deduplication_reopen.py index 1876deefe3c..a5f8fcf54d5 100644 --- a/unittests/test_utils_deduplication_reopen.py +++ b/unittests/test_utils_deduplication_reopen.py @@ -17,7 +17,7 @@ def setUp(self): self.finding_a = Finding.objects.get(id=2) self.finding_a.pk = None self.finding_a.duplicate = False - self.finding_a.mitigated = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + self.finding_a.mitigated = datetime.datetime(1970, 1, 1, tzinfo=datetime.UTC) self.finding_a.is_mitigated = True self.finding_a.false_p = True self.finding_a.active = False diff --git a/unittests/tools/test_arachni_parser.py b/unittests/tools/test_arachni_parser.py index 337200796ea..266d45dc05d 100644 --- a/unittests/tools/test_arachni_parser.py +++ b/unittests/tools/test_arachni_parser.py @@ -20,7 +20,7 @@ def test_parser_has_one_finding(self): self.assertEqual("Cross-Site Scripting (XSS)", finding.title) self.assertEqual(79, finding.cwe) self.assertEqual("High", finding.severity) - self.assertEqual(datetime.datetime(2017, 11, 14, 2, 57, 29, tzinfo=datetime.timezone.utc), finding.date) + self.assertEqual(datetime.datetime(2017, 11, 14, 2, 57, 29, tzinfo=datetime.UTC), finding.date) def test_parser_has_many_finding(self): with open("unittests/scans/arachni/dd.com.afr.json", encoding="utf-8") as testfile: diff --git a/unittests/tools/test_bugcrowd_parser.py b/unittests/tools/test_bugcrowd_parser.py index 5e66c9c6c7b..87a3083ffb2 100644 --- a/unittests/tools/test_bugcrowd_parser.py +++ b/unittests/tools/test_bugcrowd_parser.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from dojo.models import Test from dojo.tools.bugcrowd.parser import BugCrowdParser @@ -24,7 +24,7 @@ def test_parse_file_with_one_vuln_has_one_findings(self): for endpoint in finding.unsaved_endpoints: endpoint.clean() self.assertEqual(1, len(findings)) - self.assertEqual(findings[0].date, datetime(2020, 3, 1, 6, 15, 6, tzinfo=timezone.utc)) + self.assertEqual(findings[0].date, datetime(2020, 3, 1, 6, 15, 6, tzinfo=UTC)) def test_parse_file_with_multiple_vuln_has_multiple_finding(self): with open("unittests/scans/bugcrowd/BugCrowd-many.csv", encoding="utf-8") as testfile: diff --git a/unittests/tools/test_dependency_check_parser.py b/unittests/tools/test_dependency_check_parser.py index 8f39cc6f707..620e6adfc62 100644 --- a/unittests/tools/test_dependency_check_parser.py +++ b/unittests/tools/test_dependency_check_parser.py @@ -1,5 +1,5 @@ import logging -from datetime import datetime, timezone +from datetime import UTC, datetime from os import path from dateutil.tz import tzlocal, tzoffset @@ -271,7 +271,7 @@ def test_parse_java_6_5_3(self): ) self.assertEqual(items[i].severity, "Low") self.assertEqual(items[i].file_path, "log4j-api-2.12.4.jar") - self.assertEqual(items[i].date, datetime(2022, 1, 15, 14, 31, 13, 42600, tzinfo=timezone.utc)) + self.assertEqual(items[i].date, datetime(2022, 1, 15, 14, 31, 13, 42600, tzinfo=UTC)) def test_parse_file_pr6439(self): with open("unittests/scans/dependency_check/PR6439.xml", encoding="utf-8") as testfile: diff --git a/unittests/tools/test_sarif_parser.py b/unittests/tools/test_sarif_parser.py index e316ae9fe24..0ae50e659f2 100644 --- a/unittests/tools/test_sarif_parser.py +++ b/unittests/tools/test_sarif_parser.py @@ -64,7 +64,7 @@ def test_example2_report(self): 3. collections/list.h:L25\t-\tadd_core(ptr, offset, val) \tUninitialized variable `ptr` passed to method `add_core`.""" self.assertEqual(description, item.description) - self.assertEqual(datetime.datetime(2016, 7, 16, 14, 19, 1, tzinfo=datetime.timezone.utc), item.date) + self.assertEqual(datetime.datetime(2016, 7, 16, 14, 19, 1, tzinfo=datetime.UTC), item.date) for finding in findings: self.common_checks(finding) @@ -175,7 +175,7 @@ def test_example_report_scanlift_bash(self): item.file_path, ) self.assertIsNone(item.unsaved_vulnerability_ids) - self.assertEqual(datetime.datetime(2021, 3, 8, 15, 39, 40, tzinfo=datetime.timezone.utc), item.date) + self.assertEqual(datetime.datetime(2021, 3, 8, 15, 39, 40, tzinfo=datetime.UTC), item.date) # finding 6 with self.subTest(i=6): finding = findings[6] @@ -207,7 +207,7 @@ def test_example_report_taint_python(self): item.file_path, ) self.assertIsNone(item.unsaved_vulnerability_ids) - self.assertEqual(datetime.datetime(2021, 3, 8, 15, 46, 16, tzinfo=datetime.timezone.utc), item.date) + self.assertEqual(datetime.datetime(2021, 3, 8, 15, 46, 16, tzinfo=datetime.UTC), item.date) self.assertEqual( "scanFileHash:4bc9f13947613303|scanPrimaryLocationHash:1a8bbb28fe7380df|scanTagsHash:21de8f8d0eb8d9b2", finding.unique_id_from_tool, @@ -246,7 +246,7 @@ def test_njsscan(self): finding.file_path, ) self.assertIsNone(finding.unsaved_vulnerability_ids) - self.assertEqual(datetime.datetime(2021, 3, 23, 0, 10, 48, tzinfo=datetime.timezone.utc), finding.date) + self.assertEqual(datetime.datetime(2021, 3, 23, 0, 10, 48, tzinfo=datetime.UTC), finding.date) self.assertEqual(327, finding.cwe) # finding 1 finding = findings[1] @@ -255,7 +255,7 @@ def test_njsscan(self): finding.file_path, ) self.assertEqual(235, finding.line) - self.assertEqual(datetime.datetime(2021, 3, 23, 0, 10, 48, tzinfo=datetime.timezone.utc), finding.date) + self.assertEqual(datetime.datetime(2021, 3, 23, 0, 10, 48, tzinfo=datetime.UTC), finding.date) self.assertEqual(798, finding.cwe) for finding in findings: self.common_checks(finding) diff --git a/unittests/tools/test_stackhawk_parser.py b/unittests/tools/test_stackhawk_parser.py index da043f94104..7f63ea1d458 100644 --- a/unittests/tools/test_stackhawk_parser.py +++ b/unittests/tools/test_stackhawk_parser.py @@ -6,7 +6,7 @@ class TestStackHawkParser(DojoTestCase): - __test_datetime = datetime.datetime(2022, 2, 16, 23, 7, 19, 575000, datetime.timezone.utc) + __test_datetime = datetime.datetime(2022, 2, 16, 23, 7, 19, 575000, datetime.UTC) def test_invalid_json_format(self): with open("unittests/scans/stackhawk/invalid.json", encoding="utf-8") as testfile: From 730dd979bd56060eb48e3227aeed24429eb71837 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:53:08 -0500 Subject: [PATCH 13/74] Bump django from 5.0.8 to 5.1.2 (#11025) Bumps [django](https://github.com/django/django) from 5.0.8 to 5.1.2. - [Commits](https://github.com/django/django/compare/5.0.8...5.1.2) --- updated-dependencies: - dependency-name: django dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3e4b52d094d..0f5f961016a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ django-slack==5.19.0 git+https://github.com/DefectDojo/django-tagging@develop#egg=django-tagging django-watson==1.6.3 django-prometheus==2.3.1 -Django==5.0.8 +Django==5.1.2 djangorestframework==3.15.2 html2text==2024.2.26 humanize==4.11.0 From 3206efb806105f84bb5205d272bbb8b0301fd00c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:35:38 -0500 Subject: [PATCH 14/74] Bump boto3 from 1.35.36 to 1.35.37 (#11037) Bumps [boto3](https://github.com/boto/boto3) from 1.35.36 to 1.35.37. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.36...1.35.37) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0f5f961016a..7a8f53dbaed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.36 # Required for Celery Broker AWS (SQS) support +boto3==1.35.37 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.1 fontawesomefree==6.6.0 From 2ec7cb598b5d05ead81a7317efbb8747f9c04206 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:36:27 -0500 Subject: [PATCH 15/74] Update manusa/actions-setup-minikube action from v2.12.0 to v2.13.0 (.github/workflows/k8s-tests.yml) (#11036) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/k8s-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/k8s-tests.yml b/.github/workflows/k8s-tests.yml index d2da08eb7fd..60f8bc3c38c 100644 --- a/.github/workflows/k8s-tests.yml +++ b/.github/workflows/k8s-tests.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Minikube - uses: manusa/actions-setup-minikube@v2.12.0 + uses: manusa/actions-setup-minikube@v2.13.0 with: minikube version: 'v1.33.1' kubernetes version: ${{ matrix.k8s }} From ac6e327d7fc4dba78adfc616065c22cbea316340 Mon Sep 17 00:00:00 2001 From: testaccount90009 <122134756+testaccount90009@users.noreply.github.com> Date: Fri, 11 Oct 2024 08:07:19 -0700 Subject: [PATCH 16/74] Mend SCA imports contain locations which are similar to filePaths for SAST scans (#11001) * add impact add impact since it is unused * Update test_asff_parser.py * Update parser.py * Update parser.py * Mend SCA imports contain locations which are similar to filePaths for the SAST scans This code will use the 'locations' for SCA scan outputs to do the same thing that's done for SAST 'filePaths'. Since a Finding report will either be from SAST or SCA, it is unlikely that a collision will happen, since those findings are inherently different from Mend. Since the filepaths is already being joined for the SAST implementation, if it is indeed SCA results instead, the same thing will happen except now with the appropriate locations of the library and vulnerability. Note: this is not from Mend Platform or the CLI Agent output, but rather the Mend SCA portal. There is a new Platform API that combines both SAST and SCA vulnerabilities, so a new parser at some point for that would be good, and then it's possible to rename this to 'Legacy' for the Mend parser, since the 'Platform' should be the new. * Update parser.py * adding unit test for mend_sca_vulns from Mend SCA portal Mend has gone through some updates. Historically they've been SAST and then SCA, with their own separate portals. They are joining to a Mend Platform that contains both SAST+SCA+other vulnerabilities. This parser originally looks like it was based on Mend SAST, but I have been using it for SCA also since the vulnerabilities.json output files were similarly structured. This parser change hopes to update this to extract the location and path from an SCA.json and provide that as the file path. SAST calls this in a different way than SCA, which is why I think file path can be reused for both - depending on the file context found. I hope this code reflects that goal. To note: this was not a CLI or Unified Agent generated output file, but rather from downloading the Mend SCA portal API vulnerability data and uploading the returned vuln.json files using this parser. There may be a need in the future to add a parser that can correctly accept the updated format from the Mend Portal which contains combined vulnerability data sets, and the API response .json is different, so the parser does not work for the new Mend Platform returned .json, as experienced. * Update test_mend_parser.py --- dojo/tools/mend/parser.py | 18 ++++++++ unittests/scans/mend/mend_sca_vuln.json | 56 +++++++++++++++++++++++++ unittests/tools/test_mend_parser.py | 8 ++++ 3 files changed, 82 insertions(+) create mode 100644 unittests/scans/mend/mend_sca_vuln.json diff --git a/dojo/tools/mend/parser.py b/dojo/tools/mend/parser.py index 75ed871a6a1..6bcc96f7501 100644 --- a/dojo/tools/mend/parser.py +++ b/dojo/tools/mend/parser.py @@ -102,6 +102,24 @@ def _build_common_output(node, lib_name=None): "Error handling local paths for vulnerability.", ) + locations = [] + if "locations" in node: + try: + locations_node = node.get("locations", []) + for location in locations_node: + path = location.get("path") + if path is not None: + locations.append(path) + except Exception: + logger.exception( + "Error handling local paths for vulnerability.", + ) + + if locations: + filepaths = locations + else: + filepaths = filepaths + new_finding = Finding( title=title, test=test, diff --git a/unittests/scans/mend/mend_sca_vuln.json b/unittests/scans/mend/mend_sca_vuln.json new file mode 100644 index 00000000000..6af95cb315c --- /dev/null +++ b/unittests/scans/mend/mend_sca_vuln.json @@ -0,0 +1,56 @@ +{ + "vulnerabilities": [ + { + "name": "WS-2019-0379", + "type": "WS", + "severity": "medium", + "score": "6.5", + "cvss3_severity": "MEDIUM", + "cvss3_score": "6.5", + "publishDate": "2019-05-20", + "lastUpdatedDate": "2020-03-05", + "scoreMetadataVector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "description": "Apache commons-codec before version \\u201ccommons-codec-1.13-RC1\\u201d is vulnerable to information disclosure due to Improper Input validation.", + "project": "mend-test-sca-project", + "product": "mend-test-sca-product", + "cvss3Attributes": { + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "userInteraction": "NONE", + "privilegesRequired": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "LOW", + "integrityImpact": "LOW", + "availabilityImpact": "NONE" + }, + "library": { + "keyUuid": "e4ad5291-19e0-4907-9cf1-5ce5a1746e89", + "filename": "commons-codec-1.6.jar", + "type": "JAVA_ARCHIVE", + "description": "", + "sha1": "b7f0fc8f61ecadeb3695f0b9464755eee44374d4", + "name": "commons-codec-1.6", + "artifactId": "commons-codec-1.6.jar", + "version": "1.6", + "groupId": "commons-codec-1.6", + "architecture": "", + "languageVersion": "" + }, + "topFix": { + "vulnerability": "WS-2019-0379", + "type": "UPGRADE_VERSION", + "origin": "WHITESOURCE_EXPERT", + "url": "https://github.com/apache/commons-codec/commit/48b615756d1d770091ea3322eefc08011ee8b113", + "fixResolution": "Upgrade to version commons-codec:commons-codec:1.13", + "date": "2019-05-20 15:39:18", + "message": "Upgrade to version" + }, + "locations": [ + { + "matchType": "Exact Match", + "path": "D:\\MendRepo\\test-product\\test-project\\test-project-subcomponent\\path\\to\\the\\Java\\commons-codec-1.6_donotuse.jar" + } + ] + } + ] +} \ No newline at end of file diff --git a/unittests/tools/test_mend_parser.py b/unittests/tools/test_mend_parser.py index 393dd4097c1..1cd8cc11dd7 100644 --- a/unittests/tools/test_mend_parser.py +++ b/unittests/tools/test_mend_parser.py @@ -35,3 +35,11 @@ def test_parse_file_with_multiple_vuln_cli_output(self): parser = MendParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(20, len(findings)) + + def test_parse_file_with_one_sca_vuln_finding(self): + with open("unittests/scans/mend/mend_sca_vuln.json", encoding="utf-8") as testfile: + parser = MendParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(1, len(findings)) + finding = list(findings)[0] + self.assertEqual("D:\\MendRepo\\test-product\\test-project\\test-project-subcomponent\\path\\to\\the\\Java\\commons-codec-1.6_donotuse.jar", finding.file_path) From 6cd1f998c93bfc734af1b1dd34e5e32de4cdcd43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 20:51:06 -0500 Subject: [PATCH 17/74] Bump boto3 from 1.35.37 to 1.35.38 (#11049) Bumps [boto3](https://github.com/boto/boto3) from 1.35.37 to 1.35.38. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.37...1.35.38) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7a8f53dbaed..ebb62de12c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.37 # Required for Celery Broker AWS (SQS) support +boto3==1.35.38 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.1 fontawesomefree==6.6.0 From 0bc4879ad15b8f7235a02f99600ba5a354249fc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 20:51:23 -0500 Subject: [PATCH 18/74] Bump vulners from 2.2.1 to 2.2.2 (#11050) Bumps vulners from 2.2.1 to 2.2.2. --- updated-dependencies: - dependency-name: vulners dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ebb62de12c7..b1e8b0be6a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -71,6 +71,6 @@ blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support boto3==1.35.38 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 -vulners==2.2.1 +vulners==2.2.2 fontawesomefree==6.6.0 PyYAML==6.0.2 From a0ac0f6586061ec12d4aa09d10682087c08b5a67 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:14:13 -0500 Subject: [PATCH 19/74] Add release notes for Postgres 12 (#11035) * Add release notes for Postgres 12 * Update 2.40.md --- docs/content/en/getting_started/upgrading/2.40.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/content/en/getting_started/upgrading/2.40.md b/docs/content/en/getting_started/upgrading/2.40.md index 3420f9b8356..fd399b3f536 100644 --- a/docs/content/en/getting_started/upgrading/2.40.md +++ b/docs/content/en/getting_started/upgrading/2.40.md @@ -2,6 +2,8 @@ title: 'Upgrading to DefectDojo Version 2.40.x' toc_hide: true weight: -20241007 -description: No special instructions. +description: Breaking Change for Postgres 12. --- -There are no special instructions for upgrading to 2.40.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.40.0) for the contents of the release. +With the upgrade to Django 5.1.x, Posgres 12 will no longer be supported. Please make plans to upgrade to a later version of Postrges before upgrading to version 2.40.0 of DefectDojo. To determine which version of Postgres to target, please refer to the [end of life version schedule](https://endoflife.date/postgresql) + +Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.40.0) for the contents of the release. From 309a65f187ea41a1b7ce87de2aa934e39ba27ef0 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Tue, 15 Oct 2024 16:09:23 +0000 Subject: [PATCH 20/74] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/package.json b/components/package.json index 019e424ade3..06cdce1889b 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.39.1", + "version": "2.40.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index fde5a6c98f2..0dc36e95a1e 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.39.1" +__version__ = "2.40.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 bd105fc95fd..a0cabd6bb3d 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.39.1" +appVersion: "2.40.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.154 +version: 1.6.155-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From ab286179cd9c84e5e60219613205d99459dbe084 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:36:55 -0500 Subject: [PATCH 21/74] Bump boto3 from 1.35.38 to 1.35.40 (#11071) Bumps [boto3](https://github.com/boto/boto3) from 1.35.38 to 1.35.40. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.38...1.35.40) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 995a5ac8945..84d23d99e1d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.38 # Required for Celery Broker AWS (SQS) support +boto3==1.35.40 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.2 fontawesomefree==6.6.0 From 31cf55d08009bb1877749d51528bcbc73f3171df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:38:00 -0500 Subject: [PATCH 22/74] Bump openapitools/openapi-generator-cli from v7.8.0 to v7.9.0 (#11060) Bumps openapitools/openapi-generator-cli from v7.8.0 to v7.9.0. --- updated-dependencies: - dependency-name: openapitools/openapi-generator-cli dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.integration-tests-debian | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.integration-tests-debian b/Dockerfile.integration-tests-debian index 3a01815f820..ae890a24c15 100644 --- a/Dockerfile.integration-tests-debian +++ b/Dockerfile.integration-tests-debian @@ -1,7 +1,7 @@ # code: language=Dockerfile -FROM openapitools/openapi-generator-cli:v7.8.0@sha256:c409bfa9b276faf27726d2884b859d18269bf980cb63546e80b72f3b2648c492 AS openapitools +FROM openapitools/openapi-generator-cli:v7.9.0@sha256:bb32f5f0c9f5bdbb7b00959e8009de0230aedc200662701f05fc244c36f967ba AS openapitools FROM python:3.11.9-slim-bookworm@sha256:8c1036ec919826052306dfb5286e4753ffd9d5f6c24fbc352a5399c3b405b57e AS build WORKDIR /app RUN \ From ee451e0e7c8439d2d7d20877aebce9c7fa66f3f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:58:17 -0500 Subject: [PATCH 23/74] Bump pillow from 10.4.0 to 11.0.0 (#11079) Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.4.0 to 11.0.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/10.4.0...11.0.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 84d23d99e1d..1564756aef4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ PyGithub==1.58.2 lxml==5.3.0 Markdown==3.7 openpyxl==3.1.5 -Pillow==10.4.0 # required by django-imagekit +Pillow==11.0.0 # required by django-imagekit psycopg[c]==3.2.3 cryptography==43.0.1 python-dateutil==2.9.0.post0 From 086a80f7b019bacba49435c9dabcef43e1c208fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:58:33 -0500 Subject: [PATCH 24/74] Bump boto3 from 1.35.40 to 1.35.41 (#11081) Bumps [boto3](https://github.com/boto/boto3) from 1.35.40 to 1.35.41. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.40...1.35.41) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1564756aef4..cbcc0b84792 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.40 # Required for Celery Broker AWS (SQS) support +boto3==1.35.41 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.2 fontawesomefree==6.6.0 From bfe44ce0d77c48b4bdb58b7f85c0cf778cfd5a8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:58:53 -0500 Subject: [PATCH 25/74] Bump sqlalchemy from 2.0.35 to 2.0.36 (#11082) Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.35 to 2.0.36. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cbcc0b84792..85728cc4a52 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ python-dateutil==2.9.0.post0 pytz==2024.2 redis==5.1.1 requests==2.32.3 -sqlalchemy==2.0.35 # Required by Celery broker transport +sqlalchemy==2.0.36 # Required by Celery broker transport urllib3==1.26.18 uWSGI==2.0.26 vobject==0.9.8 From 3e9b81ebb3b1d86e65ffaaf606d3b31816d52c4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:53:55 -0400 Subject: [PATCH 26/74] Bump boto3 from 1.35.41 to 1.35.42 (#11088) Bumps [boto3](https://github.com/boto/boto3) from 1.35.41 to 1.35.42. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.41...1.35.42) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 85728cc4a52..53e7b3ec0e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.41 # Required for Celery Broker AWS (SQS) support +boto3==1.35.42 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.2 fontawesomefree==6.6.0 From eb61458ec862367d1387cfa64e2c5de952e4aeab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:45:01 -0500 Subject: [PATCH 27/74] Bump boto3 from 1.35.42 to 1.35.43 (#11091) Bumps [boto3](https://github.com/boto/boto3) from 1.35.42 to 1.35.43. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.42...1.35.43) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 53e7b3ec0e8..d1e0c9a75d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.42 # Required for Celery Broker AWS (SQS) support +boto3==1.35.43 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.2 fontawesomefree==6.6.0 From d46ced1e35266034422b94d0b15d9917fa087099 Mon Sep 17 00:00:00 2001 From: venegator Date: Sat, 19 Oct 2024 22:39:28 +0200 Subject: [PATCH 28/74] Fixing error in Excel Exporter (#11087) Co-authored-by: veneber --- dojo/reports/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dojo/reports/views.py b/dojo/reports/views.py index b9505ada877..667cb0bb6ac 100644 --- a/dojo/reports/views.py +++ b/dojo/reports/views.py @@ -948,6 +948,7 @@ def get(self, request): except Exception as exc: logger.error("Error in attribute: " + str(exc)) cell = worksheet.cell(row=row_num, column=col_num, value=key) + col_num += 1 continue cell = worksheet.cell(row=row_num, column=col_num, value="found_by") cell.font = font_bold @@ -999,6 +1000,7 @@ def get(self, request): except Exception as exc: logger.error("Error in attribute: " + str(exc)) worksheet.cell(row=row_num, column=col_num, value="Value not supported") + col_num += 1 continue worksheet.cell(row=row_num, column=col_num, value=finding.test.test_type.name) col_num += 1 From e24aa1bd7aa73e9bb31e468b2b63339f2ec1848e Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:46:37 +0200 Subject: [PATCH 29/74] Ruff: Fix #11090 (#11093) * Bump ruff from 0.6.9 to 0.7.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.9 to 0.7.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.6.9...0.7.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump boto3 from 1.35.42 to 1.35.43 (#11091) Bumps [boto3](https://github.com/boto/boto3) from 1.35.42 to 1.35.43. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.42...1.35.43) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Ruff: Fix #11090 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-lint.txt | 2 +- ruff.toml | 1 - .../tools/test_appcheck_web_application_scanner_parser.py | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index 0e4ee0a0eae..4228b8f407e 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1 +1 @@ -ruff==0.6.9 \ No newline at end of file +ruff==0.7.0 \ No newline at end of file diff --git a/ruff.toml b/ruff.toml index 9a34bf6e005..25e456a5488 100644 --- a/ruff.toml +++ b/ruff.toml @@ -75,7 +75,6 @@ select = [ "TRY003", "TRY004", "TRY2", - "TRY302", "FLY", "NPY", "FAST", diff --git a/unittests/tools/test_appcheck_web_application_scanner_parser.py b/unittests/tools/test_appcheck_web_application_scanner_parser.py index 5c3e7dc96b9..9360eb9209f 100644 --- a/unittests/tools/test_appcheck_web_application_scanner_parser.py +++ b/unittests/tools/test_appcheck_web_application_scanner_parser.py @@ -1,3 +1,5 @@ +import string + from django.test import TestCase from dojo.models import Finding, Test @@ -548,7 +550,7 @@ def test_appcheck_web_application_scanner_parser_non_printable_escape(self): for test_string, expected in [ ("", ""), ( - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c", + string.printable, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\\x0b\\x0c", ), ("'!Test String?'\"\"", "'!Test String?'\"\""), From 765bc1b6034cd150af684a8d8bab01fca06328e0 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 21 Oct 2024 15:28:51 +0000 Subject: [PATCH 30/74] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/package.json b/components/package.json index f0e2d5af2c6..06cdce1889b 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.39.2", + "version": "2.40.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index f21e8362faf..0dc36e95a1e 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.39.2" +__version__ = "2.40.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 95d4a96d0bd..ae1c256e0ee 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.39.2" +appVersion: "2.40.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.155 +version: 1.6.156-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From 7360f48da2411099e821bbefc56ca278e60d9584 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 06:56:13 +0200 Subject: [PATCH 31/74] Bump boto3 from 1.35.43 to 1.35.45 (#11119) Bumps [boto3](https://github.com/boto/boto3) from 1.35.43 to 1.35.45. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.43...1.35.45) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d1e0c9a75d9..20e75fdda90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.43 # Required for Celery Broker AWS (SQS) support +boto3==1.35.45 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.2 fontawesomefree==6.6.0 From f72614d5b7c4f07c3eb7094b0bee447ea859109b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 06:57:12 +0200 Subject: [PATCH 32/74] Bump packageurl-python from 0.15.6 to 0.16.0 (#11118) Bumps [packageurl-python](https://github.com/package-url/packageurl-python) from 0.15.6 to 0.16.0. - [Release notes](https://github.com/package-url/packageurl-python/releases) - [Changelog](https://github.com/package-url/packageurl-python/blob/main/CHANGELOG.rst) - [Commits](https://github.com/package-url/packageurl-python/compare/v0.15.6...v0.16.0) --- updated-dependencies: - dependency-name: packageurl-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 20e75fdda90..a52c461dc09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,7 +48,7 @@ social-auth-core==4.5.4 gitpython==3.1.43 python-gitlab==4.13.0 cpe==1.3.1 -packageurl-python==0.15.6 +packageurl-python==0.16.0 django-crum==0.7.9 JSON-log-formatter==1.1 django-split-settings==1.3.2 From b6a61bcd01f9c0c8c0d2e9ecc56f7a0313ad5e2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:08:40 +0200 Subject: [PATCH 33/74] Bump vulners from 2.2.2 to 2.2.3 (#11105) Bumps vulners from 2.2.2 to 2.2.3. --- updated-dependencies: - dependency-name: vulners dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a52c461dc09..f69114a7445 100644 --- a/requirements.txt +++ b/requirements.txt @@ -71,6 +71,6 @@ blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support boto3==1.35.45 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 -vulners==2.2.2 +vulners==2.2.3 fontawesomefree==6.6.0 PyYAML==6.0.2 From e98d81d7f6c1b06b22c22ba5b2bc595e04f34b92 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 09:55:47 -0500 Subject: [PATCH 34/74] Update gcr.io/cloudsql-docker/gce-proxy Docker tag from 1.37.0 to v1.37.1 (helm/defectdojo/values.yaml) (#11120) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- helm/defectdojo/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/defectdojo/values.yaml b/helm/defectdojo/values.yaml index 67c41eeab3d..b2d0422bc2c 100644 --- a/helm/defectdojo/values.yaml +++ b/helm/defectdojo/values.yaml @@ -454,7 +454,7 @@ cloudsql: image: # set repo and image tag of gce-proxy repository: gcr.io/cloudsql-docker/gce-proxy - tag: 1.37.0 + tag: 1.37.1 pullPolicy: IfNotPresent # set CloudSQL instance: 'project:zone:instancename' instance: "" From 2311f5f49695175584bf595b2cda65ce1314daef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:01:54 -0500 Subject: [PATCH 35/74] Bump cryptography from 43.0.1 to 43.0.3 (#11106) Bumps [cryptography](https://github.com/pyca/cryptography) from 43.0.1 to 43.0.3. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/43.0.1...43.0.3) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f69114a7445..a70ef4f8101 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ Markdown==3.7 openpyxl==3.1.5 Pillow==11.0.0 # required by django-imagekit psycopg[c]==3.2.3 -cryptography==43.0.1 +cryptography==43.0.3 python-dateutil==2.9.0.post0 pytz==2024.2 redis==5.1.1 From 9bf5329f63506289ccdeeddd6226bd3f8a7d57f4 Mon Sep 17 00:00:00 2001 From: Adam Bertrand Date: Thu, 24 Oct 2024 17:15:45 +0200 Subject: [PATCH 36/74] Parser: Add support for importing json exports from the PTART Reporting Tool (#11038) * Initial template * Add scan file sample * Fix the basic structure of the parser * Begin unit tests and assessment parser * Fix up json files and refactor to just use findings. Group by component for the Assessment names * Use self.subTest to work out findings * Add test cases * update test files * Add support for tags and enpoints * Add files * Finish Assessment parser * Fix bugs with CVSS vector and tool id * Fix a unit test * Add PTART Deduplication settings * Add retest campaigns * Refactor and robustify screenshot parsing * Robustify attachements and screenshot naming * Refactor generation of the description * Robustify the import to avoid crashing and add comments * Add Documentation * Refactor code to implement proper guards and pythonic simplifications * Nuke unused test code * Fix formatting to flake8 standards * Fix ruff errors * Add vulnerability id from tool to vulnerability ID field * Add support for references * Add tests and fix references * fix linter complaint * fix scan files to be more representative * Update settings sha256 --- .../en/integrations/parsers/file/ptart.md | 14 + dojo/settings/.settings.dist.py.sha256sum | 2 +- dojo/settings/settings.dist.py | 1 + dojo/tools/ptart/__init__.py | 0 dojo/tools/ptart/assessment_parser.py | 62 ++ dojo/tools/ptart/parser.py | 77 ++ dojo/tools/ptart/ptart_parser_tools.py | 187 +++++ dojo/tools/ptart/retest_parser.py | 102 +++ unittests/scans/ptart/empty_with_error.json | 1 + unittests/scans/ptart/ptart_many_vul.json | 84 +++ unittests/scans/ptart/ptart_one_vul.json | 71 ++ .../scans/ptart/ptart_vuln_plus_retest.json | 125 ++++ .../ptart_vulns_with_mult_assessments.json | 107 +++ unittests/scans/ptart/ptart_zero_vul.json | 26 + unittests/tools/test_ptart_parser.py | 694 ++++++++++++++++++ 15 files changed, 1552 insertions(+), 1 deletion(-) create mode 100644 docs/content/en/integrations/parsers/file/ptart.md create mode 100644 dojo/tools/ptart/__init__.py create mode 100644 dojo/tools/ptart/assessment_parser.py create mode 100644 dojo/tools/ptart/parser.py create mode 100644 dojo/tools/ptart/ptart_parser_tools.py create mode 100644 dojo/tools/ptart/retest_parser.py create mode 100644 unittests/scans/ptart/empty_with_error.json create mode 100644 unittests/scans/ptart/ptart_many_vul.json create mode 100644 unittests/scans/ptart/ptart_one_vul.json create mode 100644 unittests/scans/ptart/ptart_vuln_plus_retest.json create mode 100644 unittests/scans/ptart/ptart_vulns_with_mult_assessments.json create mode 100644 unittests/scans/ptart/ptart_zero_vul.json create mode 100644 unittests/tools/test_ptart_parser.py diff --git a/docs/content/en/integrations/parsers/file/ptart.md b/docs/content/en/integrations/parsers/file/ptart.md new file mode 100644 index 00000000000..5ce56967493 --- /dev/null +++ b/docs/content/en/integrations/parsers/file/ptart.md @@ -0,0 +1,14 @@ +--- +title: "PTART Reports" +toc_hide: true +--- + +### What is PTART? +PTART is a Pentest and Security Auditing Reporting Tool developed by the Michelin CERT (https://github.com/certmichelin/PTART) + +### Importing Reports +Reports can be exported to JSON format from the PTART web UI, and imported into DefectDojo by using the "PTART Report" importer. + +### Sample Scan Data +Sample scan data for testing purposes can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/ptart). + diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index 476d3116d4e..205a351a621 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -42026ac47884ee26fe742e59fb7dc621b5f927ee6ee3c92daf09b97f2a740163 +2a5a93d6f848564c83e0a916ca3fa64a0467c413e50bc1da5be51d0c3c1e7f14 diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 1493afadd9d..98de4650a53 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1514,6 +1514,7 @@ def saml2_attrib_map_format(dict): "ThreatComposer Scan": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE, "Invicti Scan": DEDUPE_ALGO_HASH_CODE, "KrakenD Audit Scan": DEDUPE_ALGO_HASH_CODE, + "PTART Report": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, } # Override the hardcoded settings here via the env var diff --git a/dojo/tools/ptart/__init__.py b/dojo/tools/ptart/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/tools/ptart/assessment_parser.py b/dojo/tools/ptart/assessment_parser.py new file mode 100644 index 00000000000..02387a7d65d --- /dev/null +++ b/dojo/tools/ptart/assessment_parser.py @@ -0,0 +1,62 @@ +import dojo.tools.ptart.ptart_parser_tools as ptart_tools +from dojo.models import Finding + + +class PTARTAssessmentParser: + def __init__(self): + self.cvss_type = None + + def get_test_data(self, tree): + # Check that the report is valid, If we have no assessments, then + # return an empty list + if "assessments" not in tree: + return [] + + self.cvss_type = tree.get("cvss_type", None) + assessments = tree["assessments"] + return [finding for assessment in assessments + for finding in self.parse_assessment(assessment)] + + def parse_assessment(self, assessment): + hits = assessment.get("hits", []) + return [self.get_finding(assessment, hit) for hit in hits] + + def get_finding(self, assessment, hit): + effort = ptart_tools.parse_ptart_fix_effort(hit.get("fix_complexity")) + finding = Finding( + title=ptart_tools.parse_title_from_hit(hit), + severity=ptart_tools.parse_ptart_severity(hit.get("severity")), + effort_for_fixing=effort, + component_name=assessment.get("title", "Unknown Component"), + date=ptart_tools.parse_date_added_from_hit(hit), + ) + + # Don't add fields if they are blank + if hit["body"]: + finding.description = hit.get("body") + + if hit["remediation"]: + finding.mitigation = hit.get("remediation") + + if hit["id"]: + finding.unique_id_from_tool = hit.get("id") + finding.vuln_id_from_tool = hit.get("id") + finding.cve = hit.get("id") + + # Clean up and parse the CVSS vector + cvss_vector = ptart_tools.parse_cvss_vector(hit, self.cvss_type) + if cvss_vector: + finding.cvssv3 = cvss_vector + + if "labels" in hit: + finding.unsaved_tags = hit["labels"] + + finding.unsaved_endpoints = ptart_tools.parse_endpoints_from_hit(hit) + + # Add screenshots to files, and add other attachments as well. + finding.unsaved_files = ptart_tools.parse_screenshots_from_hit(hit) + finding.unsaved_files.extend(ptart_tools.parse_attachment_from_hit(hit)) + + finding.references = ptart_tools.parse_references_from_hit(hit) + + return finding diff --git a/dojo/tools/ptart/parser.py b/dojo/tools/ptart/parser.py new file mode 100644 index 00000000000..c52ebf4fb49 --- /dev/null +++ b/dojo/tools/ptart/parser.py @@ -0,0 +1,77 @@ +import json + +import dojo.tools.ptart.ptart_parser_tools as ptart_tools +from dojo.tools.parser_test import ParserTest +from dojo.tools.ptart.assessment_parser import PTARTAssessmentParser +from dojo.tools.ptart.retest_parser import PTARTRetestParser + + +class PTARTParser: + + """ + Imports JSON reports from the PTART reporting tool + (https://github.com/certmichelin/PTART) + """ + + def get_scan_types(self): + return ["PTART Report"] + + def get_label_for_scan_types(self, scan_type): + return "PTART Report" + + def get_description_for_scan_types(self, scan_type): + return "Import a PTART report file in JSON format." + + def get_tests(self, scan_type, scan): + data = json.load(scan) + + test = ParserTest( + name="Pen Test Report", + type="Pen Test", + version="", + ) + + # We set both to the same value for now, setting just the name doesn't + # seem to display when imported. This may cause issues with the UI in + # the future, but there's not much (read no) documentation on this. + if "name" in data: + test.name = data["name"] + " Report" + test.type = data["name"] + " Report" + + # Generate a description from the various fields in the report data + description = ptart_tools.generate_test_description_from_report(data) + + # Check that the fields are filled, otherwise don't set the description + if description: + test.description = description + + # Setting the dates doesn't seem to want to work in reality :( + # Perhaps in a future version of DefectDojo? + if "start_date" in data: + test.target_start = ptart_tools.parse_date( + data["start_date"], "%Y-%m-%d", + ) + + if "end_date" in data: + test.target_end = ptart_tools.parse_date( + data["end_date"], "%Y-%m-%d", + ) + + findings = self.get_items(data) + test.findings = findings + return [test] + + def get_findings(self, file, test): + data = json.load(file) + return self.get_items(data) + + def get_items(self, data): + # We have several main sections in the report json: Assessments and + # Retest Campaigns. I haven't been able to create multiple tests for + # each section, so we'll just merge them for now. + findings = PTARTAssessmentParser().get_test_data(data) + findings.extend(PTARTRetestParser().get_test_data(data)) + return findings + + def requires_file(self, scan_type): + return True diff --git a/dojo/tools/ptart/ptart_parser_tools.py b/dojo/tools/ptart/ptart_parser_tools.py new file mode 100644 index 00000000000..f538a81f3c5 --- /dev/null +++ b/dojo/tools/ptart/ptart_parser_tools.py @@ -0,0 +1,187 @@ +import pathlib +from datetime import datetime + +import cvss + +from dojo.models import Endpoint + +ATTACHMENT_ERROR = "Attachment data not found" +SCREENSHOT_ERROR = "Screenshot data not found" + + +def parse_ptart_severity(severity): + severity_mapping = { + 1: "Critical", + 2: "High", + 3: "Medium", + 4: "Low", + } + return severity_mapping.get(severity, "Info") # Default severity + + +def parse_ptart_fix_effort(effort): + effort_mapping = { + 1: "High", + 2: "Medium", + 3: "Low", + } + return effort_mapping.get(effort, None) + + +def parse_title_from_hit(hit): + hit_title = hit.get("title", None) + hit_id = hit.get("id", None) + + return f"{hit_id}: {hit_title}" \ + if hit_title and hit_id \ + else (hit_title or hit_id or "Unknown Hit") + + +def parse_date_added_from_hit(hit): + PTART_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" + date_added = hit.get("added", None) + return parse_date(date_added, PTART_DATETIME_FORMAT) + + +def parse_date(date, format): + try: + return datetime.strptime(date, format) if date else datetime.now() + except ValueError: + return datetime.now() + + +def parse_cvss_vector(hit, cvss_type): + cvss_vector = hit.get("cvss_vector", None) + # Defect Dojo Only supports CVSS v3 for now. + if cvss_vector: + # Similar application once CVSS v4 is supported + if cvss_type == 3: + try: + c = cvss.CVSS3(cvss_vector) + return c.clean_vector() + except cvss.CVSS3Error: + return None + return None + + +def parse_retest_status(status): + fix_status_mapping = { + "F": "Fixed", + "NF": "Not Fixed", + "PF": "Partially Fixed", + "NA": "Not Applicable", + "NT": "Not Tested", + } + return fix_status_mapping.get(status, None) + + +def parse_screenshots_from_hit(hit): + if "screenshots" not in hit: + return [] + screenshots = [parse_screenshot_data(screenshot) + for screenshot in hit["screenshots"]] + return [ss for ss in screenshots if ss is not None] + + +def parse_screenshot_data(screenshot): + try: + title = get_screenshot_title(screenshot) + data = get_screenshot_data(screenshot) + return { + "title": title, + "data": data, + } + except ValueError: + return None + + +def get_screenshot_title(screenshot): + caption = screenshot.get("caption", "screenshot") + if not caption: + caption = "screenshot" + return f"{caption}{get_file_suffix_from_screenshot(screenshot)}" + + +def get_screenshot_data(screenshot): + if ("screenshot" not in screenshot + or "data" not in screenshot["screenshot"] + or not screenshot["screenshot"]["data"]): + raise ValueError(SCREENSHOT_ERROR) + return screenshot["screenshot"]["data"] + + +def get_file_suffix_from_screenshot(screenshot): + return pathlib.Path(screenshot["screenshot"]["filename"]).suffix \ + if ("screenshot" in screenshot + and "filename" in screenshot["screenshot"]) \ + else "" + + +def parse_attachment_from_hit(hit): + if "attachments" not in hit: + return [] + files = [parse_attachment_data(attachment) + for attachment in hit["attachments"]] + return [f for f in files if f is not None] + + +def parse_attachment_data(attachment): + try: + title = get_attachement_title(attachment) + data = get_attachment_data(attachment) + return { + "title": title, + "data": data, + } + except ValueError: + # No data in attachment, let's not import this file. + return None + + +def get_attachment_data(attachment): + if "data" not in attachment or not attachment["data"]: + raise ValueError(ATTACHMENT_ERROR) + return attachment["data"] + + +def get_attachement_title(attachment): + title = attachment.get("title", "attachment") + if not title: + title = "attachment" + return title + + +def parse_endpoints_from_hit(hit): + if "asset" not in hit or not hit["asset"]: + return [] + endpoint = Endpoint.from_uri(hit["asset"]) + return [endpoint] + + +def generate_test_description_from_report(data): + keys = ["executive_summary", "engagement_overview", "conclusion"] + clauses = [clause for clause in [data.get(key) for key in keys] if clause] + description = "\n\n".join(clauses) + return description or None + + +def parse_references_from_hit(hit): + if "references" not in hit: + return None + + references = hit.get("references", []) + all_refs = [get_transformed_reference(ref) for ref in references] + clean_refs = [tref for tref in all_refs if tref] + if not clean_refs: + return None + return "\n".join(clean_refs) + + +def get_transformed_reference(reference): + title = reference.get("name", "Reference") + url = reference.get("url", None) + if not url: + if not title: + return url + return None + return f"{title}: {url}" diff --git a/dojo/tools/ptart/retest_parser.py b/dojo/tools/ptart/retest_parser.py new file mode 100644 index 00000000000..812a458344a --- /dev/null +++ b/dojo/tools/ptart/retest_parser.py @@ -0,0 +1,102 @@ +import dojo.tools.ptart.ptart_parser_tools as ptart_tools +from dojo.models import Finding + + +def generate_retest_hit_title(hit, original_hit): + # Fake a title for the retest hit with the fix status if available + title = original_hit.get("title", "") + hit_id = hit.get("id", None) + if "status" in hit: + title = f"{title} ({ptart_tools.parse_retest_status(hit['status'])})" + fake_retest_hit = { + "title": title, + "id": hit_id, + } + return ptart_tools.parse_title_from_hit(fake_retest_hit) + + +class PTARTRetestParser: + def __init__(self): + self.cvss_type = None + + def get_test_data(self, tree): + if "retests" in tree: + self.cvss_type = tree.get("cvss_type", None) + retests = tree["retests"] + else: + return [] + + return [finding for retest in retests + for finding in self.parse_retest(retest)] + + def parse_retest(self, retest): + hits = retest.get("hits", []) + # Get all the potential findings, valid or not. + all_findings = [self.get_finding(retest, hit) for hit in hits] + # We want to make sure we include only valid findings for a retest. + return [finding for finding in all_findings if finding is not None] + + def get_finding(self, retest, hit): + + # The negatives are a bit confusing, but we want to skip hits that + # don't have an original hit. Hit is invalid in a retest if not linked + # to an original. + if "original_hit" not in hit or not hit["original_hit"]: + return None + + # Get the original hit from the retest + original_hit = hit["original_hit"] + + # Set the Finding title to the original hit title with the retest + # status if available. We don't really have any other places to set + # this field. + finding_title = generate_retest_hit_title(hit, original_hit) + + # As the retest hit doesn't have a date added, use the start of the + # retest campaign as something that's close enough. + finding = Finding( + title=finding_title, + severity=ptart_tools.parse_ptart_severity( + original_hit.get("severity"), + ), + effort_for_fixing=ptart_tools.parse_ptart_fix_effort( + original_hit.get("fix_complexity"), + ), + component_name=f"Retest: {retest.get('name', 'Retest')}", + date=ptart_tools.parse_date( + retest.get("start_date"), + "%Y-%m-%d", + ), + ) + + # Don't add the fields if they are blank. + if hit["body"]: + finding.description = hit.get("body") + + if original_hit["remediation"]: + finding.mitigation = original_hit.get("remediation") + + if hit["id"]: + finding.unique_id_from_tool = hit.get("id") + finding.vuln_id_from_tool = original_hit.get("id") + finding.cve = original_hit.get("id") + + cvss_vector = ptart_tools.parse_cvss_vector( + original_hit, + self.cvss_type, + ) + if cvss_vector: + finding.cvssv3 = cvss_vector + + if "labels" in original_hit: + finding.unsaved_tags = original_hit["labels"] + + finding.unsaved_endpoints = ptart_tools.parse_endpoints_from_hit( + original_hit, + ) + + # We only have screenshots in a retest. Refer to the original hit for + # the attachments. + finding.unsaved_files = ptart_tools.parse_screenshots_from_hit(hit) + + return finding diff --git a/unittests/scans/ptart/empty_with_error.json b/unittests/scans/ptart/empty_with_error.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/unittests/scans/ptart/empty_with_error.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/unittests/scans/ptart/ptart_many_vul.json b/unittests/scans/ptart/ptart_many_vul.json new file mode 100644 index 00000000000..1e6afebcff5 --- /dev/null +++ b/unittests/scans/ptart/ptart_many_vul.json @@ -0,0 +1,84 @@ +{ + "name": "Test", + "executive_summary": "Mistakes were made", + "engagement_overview": "Things were done", + "conclusion": "Things should be put right", + "scope": "test.example.com", + "client": "Test Client", + "start_date": "2024-08-11", + "end_date": "2024-08-16", + "cvss_type": 3, + "tools": [ + "Burp Suite" + ], + "methodologies": [ + "OWASP Testing Guide V4.2" + ], + "pentesters": [ + { + "username": "hydragyrum", + "first_name": "", + "last_name": "" + } + ], + "assessments": [ + { + "title": "Test Assessment", + "hits": [ + { + "id": "PTART-2024-00002", + "title": "Broken Access Control", + "body": "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + "remediation": "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + "asset": "https://test.example.com", + "severity": 2, + "fix_complexity": 3, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "cvss_score": "10.0", + "added": "2024-09-06T03:33:07.908", + "labels": [ + "A01:2021-Broken Access Control", + "A04:2021-Insecure Design" + ], + "screenshots": [ + { + "caption": "Borked", + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "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AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQCUUAAgbwtcOFErJyYv1jiFi+Tsxs35O2b5e58SiB/7ToS0byJFL21uQQXLeJTY2ewCCCAAAIIIIAAAggggAACCCCAAAIIIJA3BfwuqZQ3b427QgCB2MVL5fB3P8qFwwflYnKKXEpJAQUBrxHwCwwU/6BACY4uJdFPPCxFmjf1mrExEAQQQAABBBBAAAEEEEAAAQQQQAABBBC4PgWYcXZ9Pnfu+joQ0EGzQxMnSdLBGMlXoaJENGsiZR5/6Dq4c27RVwQOfP+TxC1ZJuf37DbeVT1ugme+8vQYJwIIIIAAAggggAACCCCAAAIIIIAAAnlTgMBZ3nyu3NV1LqCXZ9QzzXTQLKLJTVKswx0SXqnida7C7XubQFSH9lKgehU5Pn2GxC1bbryzBapWYdlGb3tQjAcBBBBAAAEEEEAAAQQQQAABBBBAAIHrSCBXAmcparm48+fPuzAHBQVJSEiIS3lWC1JTUyUxMdHldH9/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IYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII+KoAgTNffXKMGwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwKMCBM48yklnCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACvipA4MxXnxzjRgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ8KgAgTOPctIZAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICArwoQOPPVJ8e4EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEPCpA4MyjnHSGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDgqwIEznz1yTFuBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABjwoQOPMoJ50hgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgj4qgCBM199cowbAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAowIEzjzKSWcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAK+KkDgzFefHONGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDwqACBM49y0hkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggICvChA489Unx7gRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQ8KkDgzKOcdIYAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIOCrAgTOfPXJMW4EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAGPChA48ygnnSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCPiqAIEzX31yjBsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMCjAgTOPMpJZwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAr4qQODMV58c40YAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEPCoAIEzj3LSGQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgK8KEDjz1SfHuBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBDwqQODMo5zZ39nadeuleu0bjU/Heztl/wW5AgIIIIAAAggggAACCCCAAAIIIIAAAggggAAC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kGcF0l9TMM/eNjeGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgKMAgTNHD44QQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSuUwECZ9fpg+e2EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEHAXyxB5njrfkfUfVqlaVO26/3RhYwwY3et8AGRECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggID4XVIJBwQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSudwGWarze3wDuHwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwBAgcMaLgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggg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AAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2Hvgrw34OTJk7J23To5depU3m/ClQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAo5A0Uhw2LZtu6xavVp27NwpRw4fkYRyCVK5UiU5r9m5UrRoRDQxEpj82pCSsk06duosBw4ekLKxsfLBogUSFxfnV4cdBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBnAuENZX6/IsvZdgjj8qW5OSgLS5apIh07nydjHxsuERHRwetc7oefGXOHCc00/ff98sv8uZbb0vvu/92unLw3ggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDAHxYIW3D23Ky/y5MTJ2X5AifMVIRvzX1bvlrxlcx59WWpUrlylvVPp5OVAywqVaqY4fWnPT1Dkrdtc473ur2HNGxQP0MdDiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCKQLhCU4u/+BAfLu+wv9+iAhPk4a1K8v8Qnxsm7tBtm4aaNocKZFR6Rd0bqd/Gvum9KkcSO/607XnS43XC8rV62W5cuXS7s2baTDVe0zUMx5/XVJ3bPXOX7xhRcSnGUQ4gACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4BPI9+BsxVdf+4Vmuj7XszOny/nnNfO1ymwdSUuT4Y89LvPeme8c1xBt4OAhsnhB+r5f5dNwp0RMjIwf+/erY6MAAAArSURBVMRp+Oa8MgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDw5wj8P5sIjyCqFeV9AAAAAElFTkSuQmCC" + } + } + ], + "attachments": [ + { + "title": "License", + "filename": "attachments/019f49df-c3f9-4faf-81b1-decc13cc19da.ptart", + "data": "TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAxNyBQYXZhbiwgRmlzamthcnMsIE1pY2hlbGluIENFUlQKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCg==" + } + ], + "references": [] + }, + { + "id": "PTART-2024-00003", + "title": "Unrated Hit", + "body": "Some hits are not rated.", + "remediation": "They can be informational or not related to a direct attack", + "asset": "https://test.example.com", + "severity": 5, + "fix_complexity": 3, + "cvss_vector": "", + "cvss_score": "", + "added": "2024-09-06T04:22:24.707", + "labels": [ + "A09:2021-Security Logging and Monitoring Failures" + ], + "screenshots": [], + "attachments": [], + "references": [] + } + ] + } + ], + "retests": [] +} \ No newline at end of file diff --git a/unittests/scans/ptart/ptart_one_vul.json b/unittests/scans/ptart/ptart_one_vul.json new file mode 100644 index 00000000000..67930bc3391 --- /dev/null +++ b/unittests/scans/ptart/ptart_one_vul.json @@ -0,0 +1,71 @@ +{ + "name": "Test", + "executive_summary": "Mistakes were made", + "engagement_overview": "Things were done", + "conclusion": "Things should be put right", + "scope": "test.example.com", + "client": "Test Client", + "start_date": "2024-08-11", + "end_date": "2024-08-16", + "cvss_type": 3, + "tools": [ + "Burp Suite" + ], + "methodologies": [ + "OWASP Testing Guide V4.2" + ], + "pentesters": [ + { + "username": "hydragyrum", + "first_name": "", + "last_name": "" + } + ], + "assessments": [ + { + "title": "Test Assessment", + "hits": [ + { + "id": "PTART-2024-00002", + "title": "Broken Access Control", + "body": "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + "remediation": "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + "asset": "https://test.example.com", + "severity": 2, + "fix_complexity": 3, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "cvss_score": "10.0", + "added": "2024-09-06T03:33:07.908", + "labels": [ + "A01:2021-Broken Access Control", + "A04:2021-Insecure Design" + ], + "screenshots": [ + { + "caption": "Borked", + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "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AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQCUUAAgbwtcOFErJyYv1jiFi+Tsxs35O2b5e58SiB/7ToS0byJFL21uQQXLeJTY2ewCCCAAAIIIIAAAggggAACCCCAAAIIIJA3BfwuqZQ3b427QgCB2MVL5fB3P8qFwwflYnKKXEpJAQUBrxHwCwwU/6BACY4uJdFPPCxFmjf1mrExEAQQQAABBBBAAAEEEEAAAQQQQAABBBC4PgWYcXZ9Pnfu+joQ0EGzQxMnSdLBGMlXoaJENGsiZR5/6Dq4c27RVwQOfP+TxC1ZJuf37DbeVT1ugme+8vQYJwIIIIAAAggggAACCCCAAAIIIIAAAnlTgMBZ3nyu3NV1LqCXZ9QzzXTQLKLJTVKswx0SXqnida7C7XubQFSH9lKgehU5Pn2GxC1bbryzBapWYdlGb3tQjAcBBBBAAAEEEEAAAQQQQAABBBBAAIHrSCBXAmcparm48+fPuzAHBQVJSEiIS3lWC1JTUyUxMdHldH9/fwkLC3MppyB7BNw9b/+AAAkLDfXoRc+ePWvZX3h4uPj5+VnW6cLTcXEyffofsmv3bildqpS0a9tWypYt47a9t1foPc308ox6ppkOmhWsU0P8gz33t+Xt98/4fEMgKDLCeDf1aJOOHpcLBw8Y+/GVvL+jb9wAo0QAAQQQQAABBBBAAAEEEEAAAQQQQACBPCeQK4Gz3n36y4xZs1wwCxYoKIvm/y358+d3qctKwZixn8uIUaMtT129fIlERkZa1lHoWQF3zztQBc52bN3ksYutXLVaHnq0i2V/n3z0gXS69x7Luq3btkunBx6WpKS0YO7QDz6S0Z+NkDvvuN3yHG8vjFu8zNjTTC/PqGeaETTz9id2/Y5Pv5v6HdXv6tHJe0S/uwTOrt/3gTtHAAEEEEAAAQQQQAABBBBAAAEEEEAgtwX8c2MAiYkJlpeNPxMvAwa+ZlmXlcLzdoEQ5/NTUlKdizjOJgF3zztFzQj0ZLKaxWjrPykpyZZ1+e7xUi+HoJmtwct9+0lCgvW7amvjrd9nN26QS2pmp97TTM/qISHgzQL6HdXvqn5n9btLQgABBBBAAAEEEEAAAQQQQAABBBBAAAEEcksgVwJn6d3szNmzZfoff6bXhDoEPCagl3bct2+/ZX86sLdx02bLOgoRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7wnkylKNGTH27T9AGjduJMWLFcuoKfUIXJNAUtKFdM9PtNiLz3aCDrg9/OjjtkOH72GffCjNmjZxKOMAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEvFvAKwNneqZPjxd7y68/TfZuPUbn8wJFihSWsLAwt0sy1qxe3e09xsbGytHjxyzrDx06ZFlOIQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCHivgNct1WijWrN2rXz9zbe2Q74RyDaB999927Lvbk91lWLFilrWUYgAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ5T8ArZ5zZmN957wO55ZYWUqliRVsR3wh4XODuDndJ0aJF5Ysvv5L4M2clICBAHrj/Pul8fyePX4sOEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwHsFvDpwptm6P/eCzJn5pxHM8F5GRubrAk2b3CT6Q0IAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHrV8Brl2q0PZJ9+/bLx8M+tR3yjQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEC2CHh94Ezf9YQvJ8qateuyBYBOEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEENACXr9Uo+0xPd+jp8z7Z46EhYbainLle/OWrTLrr9myb+9e2bt/v8TEHJTU1ItSIqqElCldWsqWKaWW/GsqbVq3FH//zMUlZ8/5W86fP+9yPw3q15eSJaNdyq0K9JguXLjgUlWvXl1jXC4VFgX/zJ0n586dc6lp2KCBREdHuZTnlYKdO/+Vbdu3u9yOttfPwJZOx8XJwoWLbIeyYeMmM++cWbBwoYSEhDgUly1bVurWqe1QxgECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgh4j4DXBc4iCxWSU6dPuwidiD0pb/7nLRn20YcuddldcPHiRRUsmyNjxo6TLdu2WV4u/ky87Ny506j75rtJou/j6aeelMcefUQiIgpanqMLExIT5bkeL1rWP/P0U/LqwAGWdfaFR48dkxde7GVfZOYfefBBGfruEPPYXSYpKcnYT86qvm/vXvJizxesqvJE2YiRo2XGrFku96IDZ4vm/WOW68Bi/wGDzOP0MtP/nCn6Y59qVKsm06dNsS8ijwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAl4kkLkpUTk04MCAABk/bozbq035bZr8/c9ct/XZUXH27Fm5/4GHpGev3m6DZlbX1cG/T4aPkFtbt5Ot21xnM9nO0TPoKpSvYDt0+F66dJnDsbsD+1lQzm10sCczae269W6bNWvW1G0dFQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAXhHwqsCZRm3YoL4xU8sd8Iu9+8qpU6fcVXu0PObgQWl3+12yPp0l+TK6oJ6Jds9998vsOWkzl5zPadumpXORcbxJLQuZmppqWWdfOHfeAvtDh/zR48dEz0jLKC1fvsKyiQ5m1qtbx7KOQgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgLwl4XeBM4w58pZ+UVfuFWaWkpPPSu08/qyqPlungXPu7OooOPF1rSlHBr+fUHm1r1qy17KpN69aW5bpw0+YtbutsFfPVflrppQUL0q/X5y5aYj27rVnTJpneqy29MVCHAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCHi7gNftcabBAgMD5Yvx4+T2u+629Fu0ZKn8+PMv8vCDD1jWe6LwlUGvS0JCQrpdVapYSapUvkGCQ4LkwIGDsmatdWDM1knPl16W+XNnS3BwsK3I+G5Q/0bRM7t0gM05LVu2XOrWqe1cbB7/u2t3huOcO3e+PND5fvMc58ylS5dk/fp1zsXGcZs27oN6lifk4cJiRYtKSEg+8w51EDe9ZN9Wtytbpkx6zX2rTr0zp1evk1NLV8g5NSMz+eQpST13RgLyF5CgyMISXquGFL65mUTUqyUq8upb98ZoEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBK5bAa8MnOmnUVkFpF4bOEDe+/Ajy4cz+D9DpLnae6uMm5lplidlsnD2nL/ln7nu91K7WV33jddelSpVKjv0GBcXL199/Y2MHjvOodx2oGevffDRxzL4jddtRcZ3gAqa3dS4kSy22NNs6bJl8tyz3R3a2x8sWLDA/tAyv2DxYstyW+GOnTstg3a6vnXLlrZm2fI9ddp0j/W7atUqj/Vl1VGLm5vL1o1pwVE9g7Dzw49aNZWP3h8qne/vZFnnK4Xn/t0ju4d+KMU63SNRd99hDjth337Z+/EIObd9h1lmy6Sovf30J3HPbjnx+3QJrVBRKgzqK+EVy9uaeOb74kU5uWK1us4+SYo5JP5h+aRAnVoScWMdCcyf3zPXuIZeLl5IUoHERElRwfegiIIuPQWoAKxfkNf+51eN/Zys7fSIMe78detKtY/ecbkHChBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgbwo4L2/3Crtp7s9KX/8+aflHmPG8ofPvyjTp/3P40sJDnrtDbfP+umnnpTXXx1oWR+hfiDv+3Ivqa9mkHXr/qxlm2++myT9+/aRsLAwh/q2bdtYBs6Wr1zt0M754B81myyjpGfO7dz5rxGMtGq7zM3+ZkWLFJZSpUpaneKxsj79X/FYX3TkWYGTCxfL+ZgYOTBqjFxMTpFoFTyLmfSjHFWzPS+luM6OtLq6DqBt7dlbSjzyiJTp8uA1zz67lJIi+774VmL/mC4Xk5IdLnn8t2nGcUC+ECn+8ENS+jF1vVxKW3oPlMR//0336gHhYRJarpwUvuN2KdG+bbptc7ryopr9ekkFJ3VKOXMmpy/P9RBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgVwT8M+1K2fiwn5+fjJuzGhjGUOr5tt2bJcxYz+3qspy2foNG+WUmjFjlZo0buw2aGbfvuUtLaR/n5ftixzyM/+a7XCsD1rdeqtLmS7QywHu33/Asi5V/bi9YuVKyzrnwvnp7HO2dOly5+bGcatWrSzLKbzOBC6JHBw3XjZ0fUaOTP4x00Ezm5IOsh35fpJs6tH3moIwyfHxsuGJZ+T4/6a4BM1s19LfqeeT5PA338nWPgNVuyT7Kq/Kp55LkLNbtsr+YSNk98cjvWpsDAYBBBBAAAEEEEAAAQQQQAABBBBAAAEEELheBbw6cKYfSlRUCflALXvnLg0fOUo2qx+fPZV++22q264+/cR62UirE/TyinrGllWaMuU3l+KyZctIZKFCLuW6YNly68CWDvJZ7Ytm1c8/c+dZ9q0LV7pZ4rBt69Zuz6Hi+hO4cPy44037iUS2vFXK9u0t1ceOlPrTfpHqo4dLmRdfkEItWoioevuUuOtf2TNirH1RpvMXL1yQjU90F/sx5FPLtJZ5+SXjmtVGfipl+/Qyloa0dXp202ZZ/8iTIldmTtnKc/q7QN16UvSejuanSPvbRS9/6B8UZA4l9q+/ZP/Xk8xjMggggAACCCCAAAIIIIAAAggggAACCCCAAAK5I+DVSzXaSDrde4/8OWOW233Hnnm+h8ybM0uCg4Ntp2T5e8rU3y3PbdSwoRHEs6y0KNT7lnXseLdM/Ppbl1q9l5meLabb2KfWaobX/02ZYl9k5JcuWy4PPtDZpXze/AUuZTpY1/Xxx2XYiM8c6latXm15zaPHjrmdYde8WROHPjhAwCaQr1QpKde3lxSsU9NWZHznr1pZ9CfqnrskfkMH2fvRp5J09KjZ5rSa+Ri7sIUUadHMLMtMZvcnI9W+Wwlm07L9+0iJ29uYxzpToHoVKXHnbXJK7X327xv/EVEz5fQyg4d++0NKdrrboW1OHpR5obuEVyrvckkdDNz+yuvGrDNdeVr9PZd9qotLOwoQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEck7A62ec2SiGffSBy75gtrojR47Iu0Pftx1m+TsuLl7iz8Rbnn9Pxw6W5ekV3nn77W6rjx8/4VLXto310ojLlq1waasL5s2b51Kul1dsY9GPnpm2Zu06l/YrVlgv9VijWjW33i6dUHDdCRS+o71L0MwZQQfVan01VvLXcgyu7R8xRvSyi5lNiQcPySm7GZN6lplz0My+r8jGDaTC64PMoqOTJpt5d5mEPfvkxPxFcuSPWRK3ZsM1LSnp7hrO5f4q0F/143fNmXlJhw6pZTBTnJs5HCcdPirH/5mvgo9LrspQd5J07IScXLbSuMezO3apwKKKLF5LUjP5kk/FXf5cxfO8lktyLgIIIIAAAggggAACCCCAAAIIIIAAAgggkN0CPjHjTCNERBSUcaNHStdu3S1NJv33R2mvlkBr1jTrs6SOqdlX7tKoUWNl0cLF7qoty2NPnbIs14V6ppdehtI+3dzcehbO0ePH5HRcnBSKiDCbJyQmyiaLJSr18orVqlY1gl4JCWkzdPSJc1WgrVHDBmYfOqNnv1mlNm1yZpnGHs8/a3X5LJVt3rJF5i9YlKVzOenqBA59+ZX4BQdJyfvSDyj7h4RIxQF9ZfOzPYy9x/RVUuLjJObrH6RC7xcyddGjU/802+nlGaPuch+QtjUseuvNcnB8MWNpRz3rTAeMCjdpZKs2vw/931Q58t0kSU1INMtsmbDKleWGNwdJSLTj36mt3hPf/sEhEqCM9L5seoac8XHq+FJyiuz6aIScmj/Ppd4/JEiK3HGHlO/p/u8ofuOWyzP/1P+DgUNSS2mGlCghld5603JGnENb5wMVNNv8Yj9J2LnTqAnMn1/q/TxJ/IJ85n9SnO+IYwQQQAABBBBAAAEEEEAAAQQQQAABBBBAwBDwqV85W9zcXB558EH5788/Wz6+Hi/2lgVz50jBggUs6zMqPGK3pJxzWx28mjl7tnNxlo+tgnTh4eFSrUpV2bZju0u/K1eulnZt04JZ7maKNW/e1Di3dctbZPqfMx36mTdvoQzo38+hbPkK69lsrVu1dGiXXQf9+/bxWNcLFy0mcOYxzYw7OjjucylQrYqxRGJ6rXXgqeQz3eXAqDFms9M6CN3reTXbymkjNLNFWub0vAXmQeH2t5n5jDI1RqtlIk+cNJqFlinp0vzQ/36Xg59/4VJuK9BBoc0vvCR1vv9KAgtk7b8ptr7cfeuZd0bQTDUIiizsEnjS9XpvN/tlKu37upiULMd/myZJalZe1aFqeUonz3O79sqO/oPkktU+bypQl3TkqGx54UUpP7C/FGvT0r5r93nV15Ze/dOCZhGFpNbEsS5jd98BNQgggAACCCCAAAIIIIAAAggggAACCCCAgPcK+MxSjTbCwW++JiWKFbcdOnzrZRb7D0xbos2hMhMHx44fz0QrzzRxd622dsEx+ystWbrE/lAt0zjf4Vgf1FZL4oWFhhrlbds47v+kC3VAzn4Wms7v27ffaG//T0hIPqlTu5Z9EfnrUKBg3ToSkC/E7Z0H5i8gfv4ZB750B8XbtxU/uz39UuJOy9ntl2crub3AlYrk05eDX/qwYJ3Mv5dBhSMlf5VKxifgyt+F7Vp6qcOD48bbDiX0hhsk+qmuUnHwa1JY/Q3a7lsHrDY/10v0fmSeTnrZxS3P9za7jXr8ETNvyxyY8I0ZNPMPCpKi93SUSm+/KZU/GCpF2t9hLvMYv3KVnFq5xnaa8Z2kloPd1ruPGTQLVAGuYvd2lIpvvSFF77xTBeoiL7dXAbS9H3xiLLno0IHVgQ6avTxAzm3fYdQGFy4idb4dL0EFC1q1pgwBBBBAAAEEEEAAAQQQQAABBBBAAAEEEPA5AZ+acaZ1Q9SyZhPGj5F7Oj1giT3n739k2u/TpePd6S8hZ3XyGTW7I6dSipu9jNq0biWjx45zGcby5ascyubMnetwrA9ua9vWLLvllhZm3j6zeMkyc+ba6jVr7avMfLOmN6mJK5kLiJgnkclzAhH160j93//PI/flr5Z1zFemrCTu3WP2lxhzSPKrGWvppVS1JKn98oU6EHatSS99uOfdD8xuirRrp5aTTAtgFWnRTJKfeUo2dOkmF5OTjeUe944Y59DGPDkTmd3qWiGlShktL6l9xVJV0DD55CmjX9vpOpAVdbcKhDml0/MvB8j9/P2l6ohPjCCgrUmhBnUlpHRJ0ctm6nR66QrR+7vZ0o5Bg0XPSNNJz2arO3mi+AVe/k9+keZqSds+PWRD12ck6dBho03MDz9JhRfdL/mo90Tb2megnNu6zWgfUry41PxyjDgHJY1K/kEAAQQQQAABBBBAAAEEEEAAAQQQQAABBHxUwOcCZ9q5dq1a8lLPHjJqzFhL9v4DBknjxq77GVk2tiuMVD8u51QKvPIDtvP19KwxPeMrKem8Q5WeLZasfsQPUrNOTp48KYeu/Nht36i1CrrZkt4PrVLFSrJr9y5bkfGtZ6rZlnxcusx6f7O2ObS/mcPAOMjzAmFVKjsEzi6ciM3wnpOOpO07qJdLtJ+1Zn/y0Vl/S3LsKfsih7wOMuWvWtkoOz53gTkLSwd/Kr7Sy6GtPtCz1W4Y+rbsGPCqUXd68SL1nRZcMwoz+c/5mBjRH3epiNqbsUz3xy2rMwpcRnW4zQycnV233uxDL/F4fv+V2aQqBl595Cdm0MxspDJV3n9HtqkZZHopx6T9B+yrHPM6aNb3VTl7ZV/FkFIlpdaEUaL3aCMhgAACCCCAAAIIIIAAAggggAACCCCAAAJ5ScAnA2f6AfR6sYfM/Gu27FT7EDmnlNRU6fFiL7npKoNnxYsXc+7KPP524pdSvnw58/haM9FRUZZd+KuZJU1vaiTzFix0qV+/YaM0bFBfFi12XLZRNwwLC5Pq1ao6nHNbuzYybrxj4GzugrT9opYutQ6ctbr1Vod+OEAgtwT81Ew1M/kHmFnnzMHRY829wpzr9PGFmHaS/8qssrhlK80mBZupPQHdzK6MuLG2CjYFyKWUVElNSBQdjMrqkoS6HzNdvGQG7nRZ7MxZxqfUC89JyU53m80ylVH/vbClS3bLSZ5elTabNKRkSQmJsl7eNl/JKKn383e2Ltx+b+3/upzdtMmoDy1XXmqOG8GeZm61qEAAAQQQQAABBBBAAAEEEEAAAQQQQAABXxbw2cBZgNov6YtxY6Tt7XeIDpQ5p3XrN8jWbZf34XGuc3dcooT1j8u6fb58+aRM6dLuTvVoedu2bSwDZ8uWLTcCZ3Mt9jdrabE0o172cdz4CQ5jO3LkiOj91YoULiybr8wesW+g94+LiiphX0QeAY8IJOxwDHIHFy2SYb+hJaPNNqlqD8OspkuXLpqnpsSeMPPhNRyDzWbFlUxw0aKSdOSocZR05HiWAmc1Ph8t4ZXKO3Sdeu6cnN2xWw5P/lnOrLsc5NJ7rgUXiZSit97s2FbtRXj0j78kbtkKOb9nj9pv7bxcSk51CL45nKAO7GfqhaqZp9eSEv/91+H0Sm+9RtDMQYQDBBBAAAEEEEAAAQQQQAABBBBAAAEEEMhLAmnTFXzwrsqWLSOD33jN7cidlzt02/BKRQm1bJu7tGTJUndVHi9v3bKlZZ9LrswQW7BQLxvnmNq2aeNYoI7q1a1jLPvoXDF//gIVVNxuGXDUwTYSAp4WuHghWc4f2O/QbajanyvDpGaDBYSHGc30coLGnmcWJ9X8Ypzab2usw6dI+7Q9w/xDQ82zUs6eM/OhGQTDg1TgzJYuqCVSPZUCwsNFz2ir9vE7UvzBtP0aD3832eESZ7bukPUPPS4HJ3wpZzdskJQzZ4x9y7RFeikl/qxZrfdB82Ta3negCtyleLJL+kIAAQQQQAABBBBAAAEEEEAAAQQQQAABBLxGwKcDZ1qxy2OPSpPGja8aVC2W5nJOuPoxO8rNEoq//f67S/vMFKSkpIjzJ9Vihpx9X3rGl5755ZzWqll0+9U+RKdOn3aukltvbeFSppd9bNb0JpfyuXPny/Lly13KdUHr1i3VvyQEPCtwbOYcuWT33gdGFDL3HMvoSsFF0/4WTsxzDRrr8/VShGHlyjp8kvbvNbsOr1bFzAcWLGDmE/ftM/NWmQtH0/ZYCy6S8Qw5qz4yKiv9aGezSZLdXmg6SLitd19zCcqAfCEScdNNKtDWWUo9213KD+yn9mEbYp5rnwmKLGQent+T/j2aDdPJhFaoaCxbqZsknzol2wYNTqc1VQgggAACCCCAAAIIIIAAAggggAACCCCAgO8K+HzgTNOPHPGp5cyqrDyW++7paHnavn37LZdPtGx8pfD/pvwmVWrUdvlUrl7LCKald27bNq4zv/QMui++/MrltArlK0hkobQfyu0b3Naurf2hkV+weLEsdjODrmmTJi7tKbh6gdQMZgRdfY/ecUZizCG1VGDSVQ0m6fBROfTFlw7nFGrR3O3eYg4N1UF4nVpm0aEvJpr5jDIJu9KWGCxYq4bZPMguAHZWzehKLyXHxprV+aLTAnhmoQcyevaZ35W9yvRMsotJl31jF6pZrlfi+4EFCkr9qb9IlXfflHLPPCklH7hXirVtJeEVK1iOIF/JtOVWz+/ebdnmagprTRgplYe+Y56iZ7/t/3qSeUwGAQQQQAABBBBAAAEEEEAAAQQQQAABBBDIKwJ5InBWVO2VNGLYRx55Jvfde4/bfl7u01/i48+4rbeviIuLlzcGW88GqVy5sgQGBto3d8m3drNk4g8//uTStp36Ad1datXyVpeqBLVn0rwFC13Ka9eqKaGh+VzKKbAWCAoOsq5QpTExB93W+WpF3JoNsuX5nrKlRz+HPbTSux8dBNr90afmrCndNrBghJR+6rH0TnOoK/tMV3O2k16q8NCvUx3qrQ5ifvjZWNJQ1/kFBki+klFms4jmacHhOLXsqf1MOLORysQuXmbuIxaYP7/oT3akszt3p11HBcj8Q0KMy8QtSZsVWqxzJ5ErwTX7MRz/e579oZmPbNxA3fjlw6Tjx+Tcrj1mnX0m8eAhtRRkV1nbuYvseCMtMGbfJvSGG4zDiPp1JPrJJ8yqo5N/lJPLVprHZBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgbwgkCcCZ/pB3H5bO7n7zjuu+ZncUKmilFNLvlml+DPx0r5DR2O5RKt6W5menfbIY0+Iuz3Wnun2pK2p2+8makm2zCar/c1s5xYvVkxKloy2Hab73c5in7R0T7jOK8uWsX5PNMvSZWlBj7zAFLd2o/w7+D9GMCpx314VPHtZ4jdsTvfWdP2mp3vI2U2O7cq+3FOCChZM91z7ygC1P1mJRx4xiw6O/0L2jnWcwWZWqsy+Cd/I4W++M4tKPvWkmdeZorc0NwNxetnBnUM+cKjXB3qW3J633zPLC7kJZJsNspg5t2uv7HhlkHl2/np1zXxI2dJm/oIKcDknHQw78v33zsXGsQ6+hVerdrlOzVrb3neA6/5wanbbjldelwsnYyUl7rSEqD0jM0qlH3tQCtSvbzbbPeRdw8osIIMAAggggAACCCCAAAIIIIAAAggggAACCPi4QPrTnnzs5t5/711ZpJYgtNoD7GpuZeTwYXJPpwcsTzly5Ii0vf0OebBzZ+n6RBeppAJtei8xPYtr27bt8vfceTJu/ATLc3VhSEg+6Xh3B7f1tgo986tu7VqyfuMmW5Hld2BAgNxo92O7VaN2bVrLt9//YFXlUNaqVUuHYw7SF4iIKCjaP8Vu7y7bGWvWrpUJX3wlT6sgaYBq4+vp8I+/mDO49L2kqCDyjoGvSaEWLaRw61uNJQODIgpIwt79cnbbDjmzfpOcXrTQXGrQdv+FbmkhRVo0sx1m+rtMlwflxLTpRoBHn3RcLYMat2ixFGjQUAVyaquxXZD4FaslYfMWIxBk6zhf2bJS8sH7bIfGt596HnrZwR2DXjPGF7d0qWx4vLsUbNpEgksUkzNr1snZtevMWWC6jwo9n3Ho42oO9g77TEJKl0o75dIlST55SpLVf0uSjqXtoaZniJXolLZUbLE2LeXofy/PMI396y9JijkgEWoGaWB4mMSvXCOnFywwx5jWeVqu6vtDZEOXpyXl7FlJTUiU9Q92kQKNGkvBRvXl3MYtErdipempr1368YfSTk4nV3XoYNnw2NOG86WUVNnyUj+pO3mi+AcHp3MWVQgggAACCCCAAAIIIIAAAggggAACCCCAgG8I5KnAWVhYmIwbM0oefuzxa9KvXauWdO/2lHw58WvLfnSgZPJPPxkf3UAHw9zNLnPu4J233pTgTP7A3K5t2wwDZ83Uj/0ZBWZua9cuw8CZvoca1a/MUHEeNMduBUqpgIieYWiVPvj4Exk+crRUqlBegkOCpclNjWVA/35WTb2+rNLr/WV7v9clcW/akn86aHJKBYr1JzMptGIlqfByj8w0dW2jgtN1J38lO94cqgJba4z6C8ePS+zMGcbH9QSR0HLlpcoHb1tViV52sPyAfrL3w2FGfZIKYulgnHMKioyUmmOHWy6T6NzW3XHCzp2iP+mlgHwhUkUtN5u/SiWzWWi5Miow2EDOrF5tlJ3dslX0xz4VbNxIBQytl0vUe6fVGPuZmvX3vFxMTjaWyzy9cKHoj33S+6tVeGOQ6Jl9mUl+apnZaqOGycauT4t+B/Rste0DB0v14a4z9zLTH20QQAABBBBAAAEEEEAAAQQQQAABBBBAAAFvEvD3psF4YiyNGzWUbnb78GS1zwH9+0q1KlUzdXpmg2YD+vWVzvervYoymVq1aplhy7ZqNllGqVHDBsbMqPTatbi5mfj5qWknpKsS6HSP+z3xdEf63diybZusW79BFixYdFV9e1NjvbRiteHvS4Ebb7zqYek9xqIe7yK1xg2XwAIFrvp82wn+wSFS7cO3pcxLPSUgzH2QRwehyvZ7WWp9OVqCixa2ne7yXUztDVh+YD8JirRoo/4U9JKENT8fae455tJBOgU6GJVR0hb51b6Cxe6/X2r/8LVD0Mx2brUPhhj1/iGO++np/ovdd69UfedNW1MV3HP9+w2JLqGCXMMlTO2r6Jx0H3o2Xc2vPneZBWg/fv8A13sJKV5Ubnj7P2aXZzdtkkP/+908JoMAAggggAACCCCAAAIIIIAAAggggAACCPiqQJ6acWZ7CIMGvCJz5vwj+2NibEVX/R2oZlVMnfKL9On3ivw5c9ZVn+98Qrenusrzz13dcm/Vq1XNcDZbm9YZB870vTRu1EiWLFvmPCzzuG027eFkXiCPZl54/ln578+/iF7CM6+nwPz5pdpH78iJeQvl4ISJomd8ZZRCK1SUCoP6qqUcy2fUNNP1UR3vEP25mJQkcWpJyLNbd4hcTFV7elWWAmpfr6DIiEz3pYNn+pN86rQkHjioZmWdl5BiRSVUzST0C8r6fx5rjvk002PIqGH5558S/blw4qSc273HGF+Ymo2m1og1Tm00e3q6XYRXKm/MmktNTJTE/TGSHBcvYWVKiw6quUv6WWfUbyG15GNGbdz1TzkCCCCAAAIIIIAAAggggAACCCCAAAIIIOCtAln/Zdhb70iNSweKJnw+Vtp3SNsvKCvDDQoKktEjR8j4CV/KsOEjLPeyyqjfCuUryFuDX5cWNzfPqKllfSu1J9TM2bMt60oUKy7R0VGWdc6F7dq1STdw1lLtnUS6egH9ruk98R7t8kSW3o+rv2Lun1G0ZQspesvNErdxs5z8e74k/PuvJJ+IVfuexUlA/gLGDK7wWjWksJrFGFGvlhng8fTI/UNCJLJxA+NzrX0HRRZS4y50rd1k6/l69lx6M+gyurheijF/VdeZZxmdRz0CCCCAAAIIIIAAAggggAACCCCAAAIIIHA9CeRK4KyAms1glYLVD+GeSlWqVJaBaj+pDz+5vIeRc7+Z3WdMn/fcs92ly2OPyM+//CpfTPwmU7OLypUrK4Ne6S+339bO+dJXddymbWu3gbO2bVpluq/WatnHIe8MtWwfFRUlxYsVs6zzRKG75x0YEOCJ7s0+wtWeTu5S/nTq9N54Vik83Po9dW7bsEF9Wbpogbz73vsy9ff0Z/84n+uzx2pZwIi6tYyPz94DA0cAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwEnA75JKTmUcZiCwb99+2X/ggMQcPCgH1HeI2ntJz06rVKmi3HBDJSmn9g3SM5FI159AQkKCbFV7mm3avEX2qGX1IgoVkvj4OClYMELq1K4lbXJoScyV7ToY+Cyld/29g758x7y3vvz0GDsCCCCAAAIIIIAAAggggAACCCCAAAJ5Q4DoThaeo55Npj8kBJwF9Oy1BvXrGx/nOo4RQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAuwX8vXt4jA4BBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBnBEgcJYzzlwFAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAywUInHn5A2J4CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACOSNA4CxnnLkKAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAlwsQOPPyB8TwEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEckaAwFnOOHMVBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABLxcgcOblD4jhIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII5IwAgbOcceYqCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACXi5A4MzLHxDDQwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQyBkBAmc548xVEMhRgfy164hfYKAc+P4nST4Vl6PX5mIIXK2Afkf1u6rfWf3ukhBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgdwSIHCWW/JcF4FsFIho3kT8gwIlbskyObdrt1y8kJSNV6NrBLIuoN9N/Y7qd1W/s/rdJSGAAAIIIIAAAggggAACCCCAAAIIIIAAArklQOAst+S5LgLZKFD01uYSHF1Kzu/ZLcenz5D4DVuYeZaN3nSdNQE900y/m/od1e+qfmf1u0tCAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRyS8Dvkkq5dXGuiwAC2ScQu3ipHJo4SZIOxki+ChUlolkTKfP4Q9l3QXpG4CoF9PKMeqaZDpqFlCotJbt1kSLNm15lLzRHAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQ8J0DgzHOW9ISA1wno4Nnh736UC4cPysXkFLmUkuJ1Y2RA16+A3tNML8+oZ5pFP/EwQbPr91XgzhFAAAEEEEAAAQQQQAABBBBAAAEEEPAaAQJnXvMoGAgC2SNw4USsnJi/WOIWL5OzGzdkz0XoFYEsCOSvXcfY08xYWrRokSz0wCkIIIAAAggggAACCCCAAAIIIIAAAggggIBnBQicedaT3hBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBHxUwN9Hx82wEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEPCoAIEzj3LSGQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgK8KEDjz1SfHuBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBDwqQODMo5x0hgACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4KsCBM589ckxbgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAY8KEDjzKCedIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII+KoAgTNffXKMGwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwKMCBM48yklnCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACvipA4MxXnxzjRgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ8KgAgTOPctIZAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICArwoQOPPVJ8e4EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEPCpA4MyjnHSGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDgqwIEznz1yTFuBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABjwoQOPMoJ50hgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgj4qgCBM199cowbAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAowIEzjzKSWcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAK+KkDgzFefHONGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDwqACBM49y0hkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggICvChA489Unx7gRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQ8KkDgzKOcdIYAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIOCrAgTOfPXJMW4EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAGPChA48ygnnSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCPiqAIEzX31yjBsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMCjAgTOPMpJZwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAr4qQODMV58c40YAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEPCoAIEzj3LSGQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgK8KEDjz1SfHuBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBDwqQODMo5zZ39nadeuleu0bjU/Heztl/wW5AgIIIIAAAggggAACCCCAAAIIIIAAAggggAAC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kGcF0l9TMM/eNjeGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgKMAgTNHD44QQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSuUwECZ9fpg+e2EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEHAXyxB5njrfkfUfVqlaVO26/3RhYwwY3et8AGRECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggID4XVIJBwQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSudwGWarze3wDuHwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwBAgcMaLgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEgjMLYVzCefl5Ok4OXsuUZIuJOfWMLguAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBALgmEBAdJ/vBQKVwoQsLD8uXSKNIu63dJpbTDnMkdOHRUYk/F58zFuAoCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIDXCxSJLChlSpbI1XHmeOBs174YOXM20bjp4kULSWREQckXEix+fn65CsHFEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEck5Az+06n3RBTsXFy7ETp40LF8gfKpXKlc65QThdKUcDZ7aZZnraXfky0RKaL8RpOBwigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghcbwKJ55Nk74HDxvZeuTnzzD+n4PWeZrblGQma5ZQ610EAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEvF9AT7bS8SOddDxJx5VyI+VY4Ozk6Tjj/vTyjMw0y41HzTURQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAe8V0PEjHUfSyRZXyunR5ljg7Oy5y/ua6T3NSAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgg4C9jiSLa4knN9dh/nWOAs6UKycS/5QoKz+57oHwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwAcFbHEkW1wpp28hxwJnthvz8/OzZflGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwBTI7ThSjgfOzDsngwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIAXCRA486KHwVAQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRyT4DAWe7Zc2UEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEvEiBw5kUPg6EggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjkngCBs9yz58oIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJeJEDgzIseBkNBAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDIPQECZ7lnz5URQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQS8SIDAmRc9DIaCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQewIEznLPnisjgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgh4kQCBMy96GAwFAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg9wQInOWePVdGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDwIgECZ170MBgKAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBA7gkQOMs9e66MAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDgRQIEzrzoYTAUBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACB3BMgcJZ79lwZAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAiwQInHnRw2AoCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACuSdA4Cz37LkyAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAFwkEetFYGIqFQO/+r8nePfuMml4vPiNtWrW0aEURAr4hcCL2pOzZu1diDh6W2NgTUiiysESXKCb16taR8LAw37gJHxvlvZ27yKVLl8TPz0+++WqsFIoo6GN3wHARQAABBBBAAAEEEEAAAQQQQAABBBBAAIGcE8izgbMFi5bIsOFjJPViiqEZ4G99q4UKRUiFCmWlerWq0vKWm6VE8WI5p5+JK8XFxZn3cO5cYibOyLtNvv52kvw2bYbp8dOkiRIeHp53bzgP3dnS5Svkux9+kgMHYtzeVYUK5aVf755SvlxZt22ouHqBlNRk86TkCxfMPBkEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABVwHraJJrO58rSUxMNAMsevC2AJrzjcSejBX9WbV6rUz+76/ynzcHSv16dZybcewFAjP++tvhOf4+Y5Y83LmTF4yMIbgTSE1NlTeGDJWNGze7a2KW79mzV156eYD069NTBbFbmOXenvlz5mxZvnKVMcybGjWUO9u38/YhMz4EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABNwLXzR5nfuL6f84mOrg2WP3I/9ecuc5VHOeywJat2yQhIcFhFDNn/e1wzIF3CVxITpbnX+rnEjQLCgqW8uXLSp3aNaVQoUIOg74kl+ST4aNl7PivHMq9+WDJsuWyes0646PzJAQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAHfFcizM87sH4n+oX7Kz9/bFxl5PSvt31175OvvJ8uOHTvN+nETJkrrVrdIYECAWUYmdwX+77dpLgM4ceKEHDh4SMqUKulSR0HuC4z5/Es5fPiwOZDAgCDp9lQX6XDHbeLvnxaz17PSfvjxF/n51ylm2z9n/iX584fLE489bJaRQQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAguwXSfr3O7it5Yf+hoaFSu1YN+fTDd6Vpk0bmCJOTL8jGTVvMYzK5K5CiAiurVq03B6EDMLY05bffbVm+vUhg1+698vc/88wR6eD1qBEfSse72jsEzXSDABWg1gGyD959SwWr057tL7/+JnHx8WYfZBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQyG6B62LGWWYQX+rxnCxdttJseujQYbmxbm3zWAdvNlzZpylfSLDUqF7NqIs/c0aWLV8lm7Zsk9B8IfLM010tZ6rpc3fs2i2HDx1RgQJ/iY6OkmpVKkv1alXMa1xLZt/+A2qvtlNmF/Xq1HIJUOjK3WofqU2bt8qBmING29JqtlatmjWkUsXyxrHVP6fj4o3zdF2RwpFSrmwZo9k5tXTiylVrZIMKMkZGRkjlShWlZo3qUiB/fqtuslw2f8FCc2+zfPlCpedz3WTYZ2OM/uYtWCK9ej53VX0bz+LfXWo21FHJH66Cp2rJQD3u0Hz5Mt2P9tb9HDp8RPSMqRrVq6qlB2tJ4UjHpQfT6zA29qSsWrNWPYsYOXfuvERFFZcblGGDG+umd5pDnX42Gzdvls2btxnvVTX1PlWvWlWKFins0M7dgR6/4bHzXymizqlWtYr6VJbwsDB3p2SqfMLEbx3aDXlzkJQpXcqhzPmgVs3q8niXh+Xrby/PDtXLNv41Z5480Kmjc1Pz2PYcDqv7SDyfJCWiikl59X42btjA8v23nahnKh4/fsI4LF+urPncjqmyZStWya7de4zxVrmhkvFsAwMd/1OZeP68bN22wzj/xIlYW7ei82vWbTCP9d94WFiocWx1zUuXLskOZb9k6XI5HX9GmqkAvt4nzTmlpKTI0hUrZf+BGDl27IT6GwuTktEl5Ua1H2N0VAnn5hwjgAACCCCAAAIIIIAAAggggAACCCCAAAIIZFHA8dfgLHaSF04rWKCAsQua/rFep4IRBRxua9++/cb+Z7pQ75Y25dcfpFffAbJ/f4xDu65dHpHAKz+U64o/Zvwl3076r8v+XLaTChYsKM+qYFvLW262FV3199LlK2ToB8PM84oULiITxg6XkJAQs2y7WorykxFjHJbOMytVJjo6Wvq//KJUrXKDfbGR//3PGfLTz/8z8mXLlpZhH7wrvfq9atmXtnmyaxe5/94OLv1ktWDq9Bnmqc2a3iQtWjSXEaPGG8G0CxfOG8GnhvVvNNu4y8ya/bdM/O4HOXf2nEOT/7sya03vt/X24FelYoXyDvX2B9rx05Fj5aAKvNgnvbSgTsHB+aTn809Jm1YtjWOrf3Sw66NhI8xArHObMBW0evrJLnJ7uzbOVeaxDhi9NvhtiVN92aepv/9pHOp34N2333C7jOX8RYtl+IhxkpKabH+6ma+ngsavD+wnelbm1aaLFy/Ktq2Xg0r63GrVdFCxZqa6ua/jnfLfn36V8+cTjfZLl62wDJzp4O9nY8ZbvoP6RD3DrcOdtynHxy2vq/dR27Vrl1F3b8e7pNWtLWTg60PM69qfpJ/HqwP6OgTS163f4PA3Z2sfo94LvU+iLfXv86L6225hHDpfUwdJh382zgwK60Z6Hz/7wJkOyo5R+739/fd8h3a2/vW3/pt8pU8vqVC+nH0xeQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIEsCFzXSzXae+3Zu0+FzC4HzXR5wxvr2Ve75N/7cJhL0My50YfDPpNxE75yGzTT7ePVUnSfDB8lXzjN0HHuy92xnh1jHzQrXry4fD56mEPQbMGiJdJ/4Jtugwy6b70XVf+Bb8jipcvcXcoov5h6Ufq88prbvrShnjH05dffpdtPZiv1Un271bJ/tnRPhzuMGX116tSwFclv0/4w8+4yw0eNk1FjJ7gEzezbnz59Wnr3HSTz1Aw3q/T33HmGo3PQzL6tDuQNHzlO9P5eVknPOur+Qm+3QTN9jg6e6LFO+PIbqy5k7rwF8mLvV1yCZvaNY0/GSo8X+6p7WWRfbORHjf1CPh420m3QTDdat36jPN7tBTmoZl5ebVqhZiGmXkwxT+t8391mPqOM3vvs4/eHyIB+vY3PU0886nLKjFlzZNAbb7l9B/UJernVKVOnS+/+r8mFZOvgoK3jE7GnpP+gwZZBM91GP48333rXo8u36gDbMBW8s3eyjcf2fe7cOXn+pX7ylwr4ptdOB+979Rkoi5ctt53KNwIIIIAAAggggAACCCCAAAIIIIAAAgggkC0CHe57SPTn9z9mZrp/3dZ2XqZPysWGzDhT+ImJifLmkPfMx1ClcuV0Z9ro4NCKlauN9npmS7lyZYzlCxMSzqkZR5f3aPp1yjRZqAJW9knP6mrUoJ76UT9FzZJap5aKO25W65lCldWycFcz82zF6jUy9P1PzD50/6OHf+gQNNNL8ekgiX1QUAfXGtavJ3pmkF5W7tixY0Yfus0HH42Qbyd+bi5dZ3Z+JaN/8NdJzyyrqGbM3FivtprdEyjr1m1SS9dtu9JKZOq0P+W+jh2M5f/Mwixk7P/4CqhZgbYlJTvcebusvbIk3vr1m+TChQvKPtjyCtrWfr8t3ahEiRLSpHEDiYgoaCxduWbt5T3UtIGeGVSmdGl1rQpmf3vVjEMdELNPejz11bKKeubQnr37Zf78RWaAY8as2VJILV/52EMPmKfo2UMDXlUBmsQEs0z3UbdObSlRrIisUcGqvXvSArjT/pghNWtWk+ZNm5jt9fKOn3421nye+jnco2Zp6eU2T6qlOlesWi1r1240xqHvZayarWT/Ts2dv0BmzZ5j9qdnUz1w/31qtlIZ2amWEl29er1s277dqNezvnRA9603BpntM5PZvGWr2UyPr2GD+uZxZjJ65pS72VN677Qxn3/h0I1+hjfWq6eWp4w0Zh/u23vA9NGzyoaPHCsDVSDOXVq0+PLfqf5brlnj8uy4M2fOyqIlyx3+Rj8ePlK+++pzo5sqN9wgDz3YycjP+usf0UFXnfSsxdtva23k9T81qlc38/aZVavXmoeF1fKnVdWSjvr69dXfky29rf62dUDblgL8A6VSpQpSr24tFdA8JOs2bDIDwcbf7ofD5asJo6V4saK2U/hGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSyRUBP/CiltoKqr7aTSS/pGIS7SSLpnZebdddF4EzPPtH7ONmncyrIdeToCdmydassU3ub6R+eddKBhHffet2+qdt861YtpW+vF1zq9Sypb7+bbJbr4MHY0Z+6LJu3ddt2eUUFUmzps9Hj5ebmTS33SLO1sX3rvbHe+f/27gTO6nn/4/inbaZpM81MTfu+FyKESGhTRERCKeYSFSolJbSotGhRIdcScS9ycyMtpOzhuvyv9g010zqV1qmU/t/Pb3x/53fOnFkdc87o9X08xvkt39/v9/09v2e6j8e87/f7HT3ebXd1s07T5AljJKpYenBn640aO8Gto8ceGvSAXHrxRfa086kjeGwYoQ4aEIwd6WuXX2Wzo+8zZtSjcmYT36gvDYgWLVkq05+Z5VTX+7y3aLHcfmu3wMtztb/kw4/c+pdf5pvOUtew0qBB+1af9cHS5dLxqrZuXbtx8NAhefHl1+yu0/Ypk8b6hWI3meBI15kaMNjX76++/oZfYDTKE1Dqze7sdbsJBju499WN3kk9pUfSvW4wpuGhNzh76ZU5ctCsiWfLlVe0kv79fN+fXuaEBnT3D3jYDeAmT3vWrHvVXAoVKuRctujDpc772nvMemaq3xpXHdq3Ee/UnTpaSkcitTD30PLugvQpJXVbpwl9+fkZbuCo0112u/EGmWZGu+koJy3ffvu9WX/tsJQsWdLZz8l/dASXLXFxcTn6Ptv62X2OHucLirXufX16S9vWl7uX9ex+i9Pe2//W1+0HDbA7m+kY69Wt49YL3NDf+1kzp0qsCeFsucNMOaq/PzYk12Byw8bNJuCu5QTC3bt1daquW7dBvv89OKthQnR73N4ns0+d1nPyhNHumoHeetqHq1atdg9pIPeiCcUCf79fe+Mt+cc/5zr19PdgzPinZIr5d4CCAAIIIIAAAggggAACCCCAAAIIIIAAAgj8GQKdzKxw883ySvr3yNFjJ8mkJ0dKZgMhdKY/raN1tei1BaGcNlM16npQ3h+d3vCFl2aLrqFkO61t6ytk1owpJjyLybbvLrrw/KChmV6oa2nZe+r+gwP7ZQjN9HhDs/bTnb166KZTNASyo1/ssWCf35rRUSNGPek+o44ZqTZ10rgMf1TftTtVtm5Ndm9x6y03ZQjN9ORV7Vqb0KmdW2/lD6uznN6uX5+7/UIze2H7tldKuXLl7K6ZynKru52XDQ2RNKyw5ZqOvl8qDZKaX3CePWUCoYXutndj8QcfuSGUHu9779/8QjNbV0MV7y+tBkZHjqQ5p3W6wp07d9qq5rnNMoRmelK/N0MH93fraWilU2nasuiD5XbTWffLG5rZEzVMADps6EC760wf+J0ZiWbLtm077KboSKWKFRLdfbtxUfML5OGHBkj3W292fqpVrWpPSeqeVHe76dlnuaGZe9Bs9O2dJHcl9XKuve3Wriag9A9jvXWDbe/d5+uz0mVyHrgFu5f32NbkFL8RYK0ua+kXmtm6GvKNHTncCUntsX/9O+vpPKdPmeAXmul1+h3rf9+9fvfRf+hDVZ4zU6pWr+brG+99//WOr7060mziuFEZfr+1vgazZ57pWz9u48ZNomExBQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ+DMEksygkgubn+/cWpcuGj7iCdm3L31GLu/z9Jie0zpa9Bq9tiCU0yY4y0lnfPbFCnnFjDT65Zf92Va/ql3G0U32omVmyj5bqlatIpdd0sLuZvjUkTA61ZwtH370sd0M+qnDGh8fOdYNzRo3aihPPTk66KiehUs+cO+hf3zX0USZlR4mILFFQ78VX31jdzN8tmqZ+ftoe2zZtt0XNtljufl8+5133eoJCQkZQqJOHX1hn04hqdMYBpaly3yeOpVluzZXBlZx95NMiKkjmPrec5cTsEVFRznnFi72jdLS0XYPD/YFW+7Fv2/osNThQwel38Pcp3bNGs4ZHanknaLx7jt7OseD/ecCM7WhTuFoi/c7UbNmDXvYCRW9wZx7wmzoCLOuXTo7P1XNcFlb1MAWHUm1fUfGPtJ1xjp1bO9en9kUmPY+gZ+HPMFNyZKlAk/neX/REt8Uk9oPwUZ72pvrqLAWZvSmLd6pEe0x+xkfF5/p9IalS5UyAWWcrSo/b9nibv+RDe2HcplMqahTqK41o1FtuebqtlIhsbzdzfA5fMggv2MfLfvEb58dBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgVAKFCxeSQQPuc5af0XtqnvKICciOHj3mPkK39ZjNWnSpGr1Gry0IpWhBaGQo2tj0bN/aQfZ+qal7zAicfc6oHj2mI4R0ijoNjZ43U+CVNNO3ZVbO9ozyCKyzO9UX4NSuVSvwdIb9ambUyQ8/rHKOp3hGFAVW/MFM3fb8C7Pd0EzfadRjw9xp/ALrb9r0o9+h1WvW+u1ntaOje4IVDXSyClIqVvT9gf/YsePBbpGjY6dOnTKj71a4ddtc2crdthuNGjaQ4jEl3EBq3vz3TGLtG8Gn9Xbv3mOrO2vIuTtBNjQw8k77Z6v89LNv5FzZuNigIaWtq5/Nz/eNhLPHdSpIbzl27KiZJjRn/bHDE241P+9ceXn2HPdWo81UglVMMNam9ZVmLbQLsgxY9KJm55zlTgGoa5jddc/90qRJQ2l75eVynrm3BkV/tJTyTOuov1OhKj97RjBqmKX9lVWpX6+OO4JT31UDqWDX1KuX9e9ofHyc7Nmb/j1K8/zjn9Wzszt3VhPfKLHAunvN/xPDO2K1fv16gVX89nWko077aP+fG5tDOCrO70HsIIAAAggggAACCCCAAAIIIIAAAggggAACRiA6KkoeHfaQDHzoEdmxY4f8bGaPGz1ugox8NH05JN3WY1oqVKjg1NVrCko5LYIzXQtr9OOPZNonJ06ckIlTZrh/ZD9g1ih74MGh8vzMKUGv0dEuRYoUCXpODx5LS5/iT7fr1a2tH1mWOrVqusHZwUOHM62r00p6y/lmZJJd+8p73G7v8UxzePK3EzJ46GP2VLafqZ7wz1u5bNkzvLsZtotHx2Q4lpcDX5nRUDp1pS2VKiaKjrYLLAkmyEpOSQ9nlpqRNoHB2bGj6cNA9boG9esGXp6jfa9jNTOCMC9l165dfpcNeWSE335WO/t+Xz9L61StUllu7HKdvDX3HfcSHW330uxXnR8NUJqe3Vi6dO4kGiwGlhuu62SC4W/Nmm4bnFMa0PywcrXzowc0GL3UjNS6/rprsg3hAu9t98uWLWs3zZpuoZs2cJ9nJGjVqr5RdO7DAjYamODMWzSQSjAhWGBJLO8LewPP6X50dO6mqgx2j8BjMTHRgYfc/d27d7vbutGwfn2//WA7Gljb/yHas8cXFgeryzEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOCPCpxRprQJyh52wrODBw/K92bJoedfNAN/zKAY3daif2/WOlq3IJXTIjjLrkOKFi0qQx68X16rUkn+8cZcp/r27dudqf90tElui3e0SGL5hGwvr5DoW6fqxPFfs61vK+iXUIM5XSstWLFrdAU7l92xNE/glF3dP+P8vPkL/G47yQSb2RX95Vy3foM7RFTre/uifCZT42V3X+/IuXIJ5bKrHvT8gYOZB6JBL/AcPH7cN8RVD99+azepYMKef741z2/NLz2no450Ckb90SlAnxo/RhLL+9qsge/EcSPl7y++IkvMtKDe6SP1ejV8f9ES56dhgwYydtRw0d+P3JSE+Fi3+h4zqjOzkV5upRxueL/P5T1r6WV2eaLn90rraCAeLDjL7PpwHd9/4KDfo+PNWnbZFa1jg7MjR3xhcXbXcR4BBBBAAAEEEEAAAQQQQAABBBBAAAEEEMirQKWKFeQxM/Ls4eEjnYEw7y5Y6N5KBzTpOa1T0Eru/iJe0N4ul+29ukM7NzjTS7/59jtp3zbzNbEyu72uJ6YjvLToNH/Bpu7zXvvT70MW9VhUNqNbateuLZs3bXYDoaGPjpaXZk2X2NiMI8FKlSrpBivaph7du3kfm2E7Le2IxJipD7U0aRQ8jMtw0Z9wQEO71avW5OnOui7a0MED3Gu9fbFp849y4QUZp1F0K2eyoam4HTHmnS4wk+pBD58R61vHTiv0ur170Hr24K8njptxjYVMaFVMypePt4fdT12rTX90Ss3FZnrR70yCn7x1m/u904r79x+Qe/o9KM9On+S3hpdOV3hXUk/nR0fxLf/kc1lppgG172gfsmbtWun7wCB55umnshzZaOvbz/SpBd93djW41LY1O+dsezrPn6VL+77PWzKZStR78x8Dpiz0rlXmrRdp2/GeNdW0bTqiUEcaZlW27/CNaCxT5o9Pt5nVsziHAAIIIIAAAggggAACCCCAAAIIIIAAAghYAZ3pbdCAvjL2yclubqF/29ZjeZ0Fzt47XJ8EZx75M8qU8VsraP3GTXkKzqKLR5n10tKDs42bNnmeEHxz0+af3BMa0mRWypj2TZ04RhYsXCLPzHrBqabTGQ4cMlxmzZicYfpIHYXy448/OfXi4svKDdddndmtI+r4oiUfub9g2jBdyy0mpnimbfxyxTfuua+//q+cPHnStSheIloOH0rvi8B1xtyLstmIN9NB2m5MTgm+9ls2t5DyCf4jD6+9un2uR3IFe4YGKnZ6Sh0C++1338uLZg20LVuSneo6Ak1HkPXsfkuwy+Xcpmc5P3ry2LFjsuzjz5zr7dpkGtqs37DJjOKrE/T6YAcvan6BE/rZ0X7vmLXnchOc6UjK/5npI7U0MItG9umd5GzHmSkgN8tPznaKaVd2Ze369X5VYs0IvIJQygWMjFy3bkO2wdnuXanuqyXEZwxa3ZNsIIAAAggggAACCCCAAAIIIIAAAggggAACIRa4+MLmknRHD2eqRr21buuxgloIzjw9pyOdNGiwJSdTpNm63s9q1arK2rXrnEPr1m/0nsqwrSHPlq3pIYeerFsn8zXRbr7xeuf6jle1lZWr18inn33h7O/cuVPGTZwiwx4a6Ozb/9Q3Se9/zKg5Lam7U0M2ZZ69/5/1qUGPLfXq1pWs1qfTervMu91xVx/nkhMnf5VPP/9CWrW81NmvVbOGu37cWjONY1bTBh4/flxmz/mHCd5+M6FWESds0mkKz2zS2Jn6UG+ogZI+L6tpH+eaUW92isJrr+ngDEVt1NB/BN+atevNfRs5bQzVf3S9u/POPceEVE2l6213OG3Ve3/73f9lGpx5nx0dHe0ExRpUJvXu55768utvchWcFTXTQdY1Qdt6463lOzOqbcPGzea7Xcu9Z2YbOvry3++mj1bTOt415XRKUvt91iklDx46JKVLZT666n8/pIdveh+dtjI/ysnffvvDj9GAT4cx2zX+/mtG7LW+slWm9922fYfo996WxmEcLWrbwCcCCCCAAAIIIIAAAggggAACCCCAAAIInF4C+rdw/fkrlMJ/hZcI1TuMHf+U363q1K7pt5/TnfZtrnCr7t27T/7+0mx3P3BjwuTpcvRomnu4Q7vW7nZWG4P695OKFSu6Vb5c8bXMm+8LHPREu9a+aSZ19M/IMePd+oEbhw8flj5mWr477u7n/Kz5PfgLrPdn7+/ctVt0fTlbOrTP3kNDrPJmzS9b5r+32G7KVW191x8+dFjUO7MyxvS/hjbvvb9I3lvgC+/aBoQWg4c+ZoKKk0Fv8/6iD+RlM+JL53JdsHCxWTPskFOvRvVqUtJMnWnLuImTJS3N1+/2uP0cNXaC2xdz5823h81csSOk34CH5L6BD8mnX3zpHvduaIBWu1YN99CvJhDUsn3HTudavX7A4EdEA5dgpUJieWfkpT13/Ogxu5njz6Set/nVHfbYKEnds9fvWOCOBpfjJkz2O+ztP52a0luGPDLCWWjSe8xuL122XFaZ6SdtueySFnYz5J/qbcs+8/seinJes6bubT759DPRKTWDlRMnTsiQRx53T+kQ6EtbXOzus4EAAggggAACCCCAAAIIIIAAAggggAACCCCQO4HTPjg7/uuvzh+l+/YfJP81I3NsKW7W+spubTJbN/Cz5aUtpHjxGPfwOybQWvLhMme0kz2owcsbc+fJZ2Z0lC2xsbFy1pmN7W6Wn7pO1YSxI/0CjhdMQLd6zVr3uriysdLAjNKxRUfrTJs5S/SdvSVl23YTqAyRn81oH13nak/qXqlRo7q3Sr5tvzN/gfssDQFatbzE3c9qo22by93T6zdsEA0CtbS4qLlfYPXpZ5/Lm2/P8wu+dNTfPPNcO5pJr2vX9gp3KsWSJUtKs3N9QUZqaqqMfOJJZ8ST1rVFw8ZnnkufQlOPabDpneKw87W+qTJ1/bH+g4bKATNyyluOHEmTUWPHy1df/8fpC+2Pep5RiAdMEKfTb27e/JOMnzDVrKG3xXu5s60hy8qVvjXiatas6RwvlxAvW35Odq5XowfNFJ8aVgWW19+c6zfysn6DuoFVst1v1LCBXHThBW49HanXu+8A+fzLFe4x74aug3ZLz7ud9bzs8Qb160uTxg3truhIrMaNfPv6fZ04ZbroSFFv0XtNmfase0i/R15790SINiokJrp30qkt//fDKnc/rxs333iD36UjR493Ru15D+p3/PHR40TDeVsuueQi93trj/GJAAIIIIAAAggggAACCCCAAAIIIIAAAgggkHOBojmvWnBr6pRn13e9PcMLnDSjNU7+lr7+lfdkkcJFzbSHA7yHcrUdVayYDBl0vzw+apx73bQZz8rTM56TKlUryykznVtKyna/dby04ojhQ9z6OdnQIOGJEcNk0MPD3erDHntCXnjuadHQTMvQwQOkV1If9z2XfLBU9CfR/LG/rKmzb98volM9ekuXLp0kpnjma4p564Z6e+myj91bnn12kxyHAB3atZE5r73hXjvfjBrrZsKHImbawOFDBvmNynllzj/l1TlvSI2a1UVH7CRvTfHrCw1abrm5i3sv3Rg88H65zQQ77vR5JmTt1v1OZ6RbbOwZsnnTT37T5ek1PXt00w+33NzlemO/zAnE9KCGLLf0SBJdu65SpYpO2LctZYfbV1qndu3afmHqLV1vkLHj00dl6SjCvmaUoI6202kf9Xu33kyJuMkuyGau13fp2T29HTrtZMuWF8uy5Z/oreXAgQNyY7deUq16FTnTBFL7TYi3avVa0WDQFm1byzyOYOrf715ZZ6ZrtMGOjqzUtus9axn72NiysjU5RTS4PZp2xD7S+Ywz6/MNDfI7qMduv+Ne1/rjTz4T/dHvc4kSMbJ1S4p7zt6w+21dJXDdMHsuFJ86verCxR+4txr66EjR4L18uTh5oO89Uq9uHfdcTjd0xGDHq9o5oxb1Gp2Ksf+gh52gvKr5N2T//v1+/aR1NKzv3+8e3aQggAACCCCAAAIIIIAAAggggAACCCCAAAII5FHgtBlxpmuXBf4EC810LaRZz0yRc8w6T3+k6FpT/e69ywku7H006Ni6NdkJTHTbFg03hg0ZaKbXSx8ZZI/n5LNhg3pyV1Ivt6oGOzqSSEdRadEAbcK4Ec6aSW4ls6Fhma7DFhiadWjfVrp36+qtmm/bK1etcdfl0ode3aFdjp9dpnRpqW6mQ7Rl8ZKP7KYzamnQwPsy9IWO3NL+8PaFBh5TnxonZ5hwx1tKligh08xxPe8tOiJM1/LyrjGl/fmACTBaBFn8cMrEMU7I472HBljaF9oW73eyatUqMmncSG9VM4LuQtE+8hZtw9KPljvhjTc007ZOfHK0JJYv51bvndRT6tXzjSDT56nDfDO1pAZQ3tCsWrUqMnPaJPFORejeKAcbGmQ9P3OqGb3o6xe9TN/3ezMqbPnHnzghX2BodokJ6l6cNcMNf72P0n6ZYEyiovyDXf0e63t4+0Gvu65TR7nphs7eW4R8u/UVl2VYQ03facuWZDMd5vY8P6/333pJq8ta+l2v/4ZpH3v7SSvoNKD6/YyKivKrzw4CCCCAAAIIIIAAAggggAACCCCAAAIIIIBA7gT+ssGZjjTKSSlWLEqqVK4kl5kpAe+56055yfzB3hs02Ht471e4cM7urWsyTZk0zm/EkL2ffmrAcl6zc+TZGZPloua+ae28dXRKRlu8bbDH9LNTx/bS4uLm7iENUma9MNvd1xEvOgpN31FH0wUrGjrp1I/33n1nhtPe5xYpEvx6e1Gxoj4b73X2fFafS5Yuc08XLVIs11Nl6qgzWzRY+MVMh2iLrnE1feoEOafpWX4Bmj2vIZOem/38dDMaqoY97PdZtUpl8/14Wtq1aS3avsCi3yV11OCt9RWtAk87+xrwzTL93fWm680IKf8Qzl6gI7Lu69NbnjGhlY4SCyzaR+PHjHBGmul3KLDou1xwfjN59YWZflNFaj2ddvIpE6bdbcLW0qYtwYo+v8v118rMqZOc6RGD1cnpsejoaJk26Unnd0tHkWVVtN0D7+8jQx683/j6vkeB19StU8sEa0/LFZe3CtoPWr9mzRoy4tGhktSrh+5mKEWK+H6vghl7L/B+j4O1S39HZ0yZ6Cx8qe/gX3z9k5tn6j00sHzwgT7y0KAHpLL5NypY0e+cjkx72fy7ValihWBV/I4VCfJ98qvADgIIIIAAAggggAACCCCAAAIIIIAAAgggcJoLFDplSn4YfL9qg/OYpo19o13y47mR8gxdS0qn5tttAh0lr1ihglQ20/Nl90f7ULdfR6Jt27HTTBW5TQqb8CCxXDknOPSGA6F+ZqTdT9d4SzZTBGpfaPDQsH5diYmJyVUzfzPTbe7YuUu2JiebPiwmNc2acHZ6zNzcaPfuVNlq+uLYsaMSHxcn1atVFQ2bclNsO6Kjop2gLDfX63voOmm7du82z483ox5riDeszU07clJ3g5lKcvOPPxq3FDO6ME2Km3eNT0iQc5ue6Rjm5B7eOtr+7eb7nJxipts0/5JpH1SpXNmZttFbLz+395rpT7Vd+rut06mGqhw8dMh8b7fJ/gP7nXtXrlRJKiSWz/OIwFC1i/sggAACCCCAAAIIIIAAAggggAACCCCAAAKhFghnpkRwFure5H4IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ5FghncOabryzPzedCBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBAq+AMFZwe9D3gABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCAEAgRnIUDkFggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgVfgOCs4Pchb4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIBACAYKzECByCwQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgYIvQHBW8PuQN0AAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEAiBAMFZCBC5BQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQMEXIDgr+H3IGyCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCIRAgOAsBIjcAgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAoOALEJwV/D7kDRBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBEIgQHAWAkRugQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggUPAFCM4Kfh/yBggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAiEQIDgLASK3QAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQKPgCBGcFvw95AwQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgRAIEJyFAJFbIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIFHwBgrOC34e8AQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQAgE8j04O3XqVAiazS0QQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQT+agLhzpHyLTiLjirm9N3RY8f/an3I+yCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCIRAwOZINlcKwS1zdYt8C85KlYxxGrZv/4FcNZDKCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACp4eAzZFsrpTfb51vwVlc7BnOu+1K/UXSjh7L7/fkeQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAhEsoPmR5khabK6U383Nt+CsZIniEl+2jPN+P23dTniW3z3N8xBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBCBXQ0EzzIy2aJ2muFI5SyCyydio/H7zp52Q5eCjNeWT5hFgpe0YZKR4dJYUKFcrPZvAsBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBMApoRKVrmun0jHakWelSMVK7epWwtSrfgzN9063bdsqefax1FrZe58EIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQIQJ6EizqpUSw9qqsARn+saHjxyVvb/sl0OH0+TY8V/DisDDEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE8l8gOqqYlCoZ46xpFq7pGb1vHbbgzNsIthFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBAIt0DhcDeA5yOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2Hvgrw34OTJk7J23To5depU3m/ClQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAo5A0Uhw2LZtu6xavVp27NwpRw4fkYRyCVK5UiU5r9m5UrRoRDQxEpj82pCSsk06duosBw4ekLKxsfLBogUSFxfnV4cdBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBnAuENZX6/IsvZdgjj8qW5OSgLS5apIh07nydjHxsuERHRwetc7oefGXOHCc00/ff98sv8uZbb0vvu/92unLw3ggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDAHxYIW3D23Ky/y5MTJ2X5AifMVIRvzX1bvlrxlcx59WWpUrlylvVPp5OVAywqVaqY4fWnPT1Dkrdtc473ur2HNGxQP0MdDiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCKQLhCU4u/+BAfLu+wv9+iAhPk4a1K8v8Qnxsm7tBtm4aaNocKZFR6Rd0bqd/Gvum9KkcSO/607XnS43XC8rV62W5cuXS7s2baTDVe0zUMx5/XVJ3bPXOX7xhRcSnGUQ4gACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4BPI9+BsxVdf+4Vmuj7XszOny/nnNfO1ymwdSUuT4Y89LvPeme8c1xBt4OAhsnhB+r5f5dNwp0RMjIwf+/erY6MAAAArSURBVMRp+Oa8MgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDw5wj8P5sIjyCqFeV9AAAAAElFTkSuQmCC" + } + } + ], + "attachments": [ + { + "title": "License", + "filename": "attachments/019f49df-c3f9-4faf-81b1-decc13cc19da.ptart", + "data": "TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAxNyBQYXZhbiwgRmlzamthcnMsIE1pY2hlbGluIENFUlQKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCg==" + } + ], + "references": [ + { + "name": "Reference", + "url": "https://ref.example.com" + } + ] + } + ] + } + ], + "retests": [] +} \ No newline at end of file diff --git a/unittests/scans/ptart/ptart_vuln_plus_retest.json b/unittests/scans/ptart/ptart_vuln_plus_retest.json new file mode 100644 index 00000000000..ad0f0dca0a3 --- /dev/null +++ b/unittests/scans/ptart/ptart_vuln_plus_retest.json @@ -0,0 +1,125 @@ +{ + "name": "Test", + "executive_summary": "Mistakes were made", + "engagement_overview": "Things were done", + "conclusion": "Things should be put right", + "scope": "test.example.com", + "client": "Test Client", + "start_date": "2024-08-11", + "end_date": "2024-08-16", + "cvss_type": 3, + "tools": [ + "Burp Suite" + ], + "methodologies": [ + "OWASP Testing Guide V4.2" + ], + "pentesters": [ + { + "username": "hydragyrum", + "first_name": "", + "last_name": "" + } + ], + "assessments": [ + { + "title": "Test Assessment", + "hits": [ + { + "id": "PTART-2024-00002", + "title": "Broken Access Control", + "body": "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + "remediation": "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + "asset": "https://test.example.com", + "severity": 2, + "fix_complexity": 3, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "cvss_score": "10.0", + "added": "2024-09-06T03:33:07.908", + "labels": [ + "A01:2021-Broken Access Control", + "A04:2021-Insecure Design" + ], + "screenshots": [ + { + "caption": "Borked", + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "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AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQCUUAAgbwtcOFErJyYv1jiFi+Tsxs35O2b5e58SiB/7ToS0byJFL21uQQXLeJTY2ewCCCAAAIIIIAAAggggAACCCCAAAIIIJA3BfwuqZQ3b427QgCB2MVL5fB3P8qFwwflYnKKXEpJAQUBrxHwCwwU/6BACY4uJdFPPCxFmjf1mrExEAQQQAABBBBAAAEEEEAAAQQQQAABBBC4PgWYcXZ9Pnfu+joQ0EGzQxMnSdLBGMlXoaJENGsiZR5/6Dq4c27RVwQOfP+TxC1ZJuf37DbeVT1ugme+8vQYJwIIIIAAAggggAACCCCAAAIIIIAAAnlTgMBZ3nyu3NV1LqCXZ9QzzXTQLKLJTVKswx0SXqnida7C7XubQFSH9lKgehU5Pn2GxC1bbryzBapWYdlGb3tQjAcBBBBAAAEEEEAAAQQQQAABBBBAAIHrSCBXAmcparm48+fPuzAHBQVJSEiIS3lWC1JTUyUxMdHldH9/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IYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII+KoAgTNffXKMGwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwKMCBM48yklnCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACvipA4MxXnxzjRgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ8KgAgTOPctIZAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICArwoQOPPVJ8e4EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEPCpA4MyjnHSGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDgqwIEznz1yTFuBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABjwoQOPMoJ50hgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgj4qgCBM199cowbAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAowIEzjzKSWcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAK+KkDgzFefHONGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDwqACBM49y0hkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggICvChA489Unx7gRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQ8KkDgzKOcdIYAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIOCrAgTOfPXJMW4EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAGPChA48ygnnSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCPiqAIEzX31yjBsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMCjAgTOPMpJZwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAr4qQODMV58c40YAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEPCoAIEzj3LSGQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgK8KEDjz1SfHuBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBDwqQODMo5zZ39nadeuleu0bjU/Heztl/wW5AgIIIIAAAggggAACCCCAAAIIIIAAAggggAAC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kGcF0l9TMM/eNjeGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgKMAgTNHD44QQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSuUwECZ9fpg+e2EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEHAXyxB5njrfkfUfVqlaVO26/3RhYwwY3et8AGRECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggID4XVIJBwQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSudwGWarze3wDuHwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwBAgcMaLgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggg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AAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2Hvgrw34OTJk7J23To5depU3m/ClQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAo5A0Uhw2LZtu6xavVp27NwpRw4fkYRyCVK5UiU5r9m5UrRoRDQxEpj82pCSsk06duosBw4ekLKxsfLBogUSFxfnV4cdBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBnAuENZX6/IsvZdgjj8qW5OSgLS5apIh07nydjHxsuERHRwetc7oefGXOHCc00/ff98sv8uZbb0vvu/92unLw3ggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDAHxYIW3D23Ky/y5MTJ2X5AifMVIRvzX1bvlrxlcx59WWpUrlylvVPp5OVAywqVaqY4fWnPT1Dkrdtc473ur2HNGxQP0MdDiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCKQLhCU4u/+BAfLu+wv9+iAhPk4a1K8v8Qnxsm7tBtm4aaNocKZFR6Rd0bqd/Gvum9KkcSO/607XnS43XC8rV62W5cuXS7s2baTDVe0zUMx5/XVJ3bPXOX7xhRcSnGUQ4gACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4BPI9+BsxVdf+4Vmuj7XszOny/nnNfO1ymwdSUuT4Y89LvPeme8c1xBt4OAhsnhB+r5f5dNwp0RMjIwf+/erY6MAAAArSURBVMRp+Oa8MgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDw5wj8P5sIjyCqFeV9AAAAAElFTkSuQmCC" + } + } + ], + "attachments": [ + { + "title": "License", + "filename": "attachments/019f49df-c3f9-4faf-81b1-decc13cc19da.ptart", + "data": "TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAxNyBQYXZhbiwgRmlzamthcnMsIE1pY2hlbGluIENFUlQKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCg==" + } + ], + "references": [] + }, + { + "id": "PTART-2024-00003", + "title": "Unrated Hit", + "body": "Some hits are not rated.", + "remediation": "They can be informational or not related to a direct attack", + "asset": "https://test.example.com", + "severity": 5, + "fix_complexity": 3, + "cvss_vector": "", + "cvss_score": "", + "added": "2024-09-06T04:22:24.707", + "labels": [ + "A09:2021-Security Logging and Monitoring Failures" + ], + "screenshots": [], + "attachments": [], + "references": [] + } + ] + } + ], + "retests": [ + { + "name": "Test Retest", + "introduction": "REEEEEEEEEEEEE-TEST!", + "conclusion": "Still broke, mate", + "start_date": "2024-09-08", + "end_date": "2024-09-13", + "hits": [ + { + "id": "PTART-2024-00002-RT", + "status": "NF", + "body": "Still borked", + "original_hit": { + "id": "PTART-2024-00002", + "title": "Broken Access Control", + "body": "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + "remediation": "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + "asset": "https://test.example.com", + "severity": 2, + "fix_complexity": 3, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "cvss_score": "10.0", + "added": "2024-09-06T03:33:07.908", + "labels": [ + "A01:2021-Broken Access Control", + "A04:2021-Insecure Design" + ] + }, + "screenshots": [ + { + "caption": "Yet another Screenshot", + "order": 0, + "screenshot": { + "filename": "screenshots_retest/ea1c661f-7366-4619-a08b-133ec1a6cfd1.png", + "data": "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" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/unittests/scans/ptart/ptart_vulns_with_mult_assessments.json b/unittests/scans/ptart/ptart_vulns_with_mult_assessments.json new file mode 100644 index 00000000000..1ad9d01d1f9 --- /dev/null +++ b/unittests/scans/ptart/ptart_vulns_with_mult_assessments.json @@ -0,0 +1,107 @@ +{ + "name": "Test", + "executive_summary": "Mistakes were made", + "engagement_overview": "Things were done", + "conclusion": "Things should be put right", + "scope": "test.example.com", + "client": "Test Client", + "start_date": "2024-08-11", + "end_date": "2024-08-16", + "cvss_type": 3, + "tools": [ + "Burp Suite" + ], + "methodologies": [ + "OWASP Testing Guide V4.2" + ], + "pentesters": [ + { + "username": "hydragyrum", + "first_name": "", + "last_name": "" + } + ], + "assessments": [ + { + "title": "New API", + "hits": [ + { + "id": "PTART-2024-00004", + "title": "HTML Injection", + "body": "HTML injection is a type of injection issue that occurs when a user is able to control an input point and is able to inject arbitrary HTML code into a vulnerable web page. This vulnerability can have many consequences, like disclosure of a user's session cookies that could be used to impersonate the victim, or, more generally, it can allow the attacker to modify the page content seen by the victims.", + "remediation": "Preventing HTML injection is trivial in some cases but can be much harder depending on the complexity of the application and the ways it handles user-controllable data.\n\nIn general, effectively preventing HTML injection vulnerabilities is likely to involve a combination of the following measures:\n\n* **Filter input on arrival**. At the point where user input is received, filter as strictly as possible based on what is expected or valid input.\n* **Encode data on output**. At the point where user-controllable data is output in HTTP responses, encode the output to prevent it from being interpreted as active content. Depending on the output context, this might require applying combinations of HTML, URL, JavaScript, and CSS encoding.", + "asset": "", + "severity": 4, + "fix_complexity": 2, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N", + "cvss_score": "8.2", + "added": "2024-09-06T09:47:16.944", + "labels": [ + "A03:2021-Injection" + ], + "screenshots": [], + "attachments": [], + "references": [] + } + ] + }, + { + "title": "Test Assessment", + "hits": [ + { + "id": "PTART-2024-00002", + "title": "Broken Access Control", + "body": "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + "remediation": "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + "asset": "https://test.example.com", + "severity": 2, + "fix_complexity": 3, + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "cvss_score": "10.0", + "added": "2024-09-06T03:33:07.908", + "labels": [ + "A01:2021-Broken Access Control", + "A04:2021-Insecure Design" + ], + "screenshots": [ + { + "caption": "Borked", + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "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AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQInPEaIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAECJzxGiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgBAic8RoggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggoAQCUUAAgbwtcOFErJyYv1jiFi+Tsxs35O2b5e58SiB/7ToS0byJFL21uQQXLeJTY2ewCCCAAAIIIIAAAggggAACCCCAAAIIIJA3BfwuqZQ3b427QgCB2MVL5fB3P8qFwwflYnKKXEpJAQUBrxHwCwwU/6BACY4uJdFPPCxFmjf1mrExEAQQQAABBBBAAAEEEEAAAQQQQAABBBC4PgWYcXZ9Pnfu+joQ0EGzQxMnSdLBGMlXoaJENGsiZR5/6Dq4c27RVwQOfP+TxC1ZJuf37DbeVT1ugme+8vQYJwIIIIAAAggggAACCCCAAAIIIIAAAnlTgMBZ3nyu3NV1LqCXZ9QzzXTQLKLJTVKswx0SXqnida7C7XubQFSH9lKgehU5Pn2GxC1bbryzBapWYdlGb3tQjAcBBBBAAAEEEEAAAQQQQAABBBBAAIHrSCBXAmcparm48+fPuzAHBQVJSEiIS3lWC1JTUyUxMdHldH9/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IYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII+KoAgTNffXKMGwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwKMCBM48yklnCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACvipA4MxXnxzjRgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ8KgAgTOPctIZAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICArwoQOPPVJ8e4EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEPCpA4MyjnHSGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDgqwIEznz1yTFuBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABjwoQOPMoJ50hgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgj4qgCBM199cowbAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAowIEzjzKSWcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAK+KkDgzFefHONGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDwqACBM49y0hkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggICvChA489Unx7gRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQ8KkDgzKOcdIYAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIOCrAgTOfPXJMW4EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAGPChA48ygnnSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCPiqAIEzX31yjBsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMCjAgTOPMpJZwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAr4qQODMV58c40YAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEPCoAIEzj3LSGQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgK8KEDjz1SfHuBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBDwqQODMo5zZ39nadeuleu0bjU/Heztl/wW5AgIIIIAAAggggAACCCCAAAIIIIAAAggggAAC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kGcF0l9TMM/eNjeGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgKMAgTNHD44QQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSuUwECZ9fpg+e2EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEHAXyxB5njrfkfUfVqlaVO26/3RhYwwY3et8AGRECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggID4XVIJBwQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSudwGWarze3wDuHwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwBAgcMaLgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBIgcMZrgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIASIHDGa4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEiBwxmuAAAIIIIAAAggg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AAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2HvAhqAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCQIEZ5HQC7QBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg7AIEZ2Hvgrw34OTJk7J23To5depU3m/ClQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAo5A0Uhw2LZtu6xavVp27NwpRw4fkYRyCVK5UiU5r9m5UrRoRDQxEpj82pCSsk06duosBw4ekLKxsfLBogUSFxfnV4cdBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBnAuENZX6/IsvZdgjj8qW5OSgLS5apIh07nydjHxsuERHRwetc7oefGXOHCc00/ff98sv8uZbb0vvu/92unLw3ggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDAHxYIW3D23Ky/y5MTJ2X5AifMVIRvzX1bvlrxlcx59WWpUrlylvVPp5OVAywqVaqY4fWnPT1Dkrdtc473ur2HNGxQP0MdDiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCKQLhCU4u/+BAfLu+wv9+iAhPk4a1K8v8Qnxsm7tBtm4aaNocKZFR6Rd0bqd/Gvum9KkcSO/607XnS43XC8rV62W5cuXS7s2baTDVe0zUMx5/XVJ3bPXOX7xhRcSnGUQ4gACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4BPI9+BsxVdf+4Vmuj7XszOny/nnNfO1ymwdSUuT4Y89LvPeme8c1xBt4OAhsnhB+r5f5dNwp0RMjIwf+/erY6MAAAArSURBVMRp+Oa8MgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDw5wj8P5sIjyCqFeV9AAAAAElFTkSuQmCC" + } + } + ], + "attachments": [ + { + "title": "License", + "filename": "attachments/019f49df-c3f9-4faf-81b1-decc13cc19da.ptart", + "data": "TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAxNyBQYXZhbiwgRmlzamthcnMsIE1pY2hlbGluIENFUlQKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCg==" + } + ], + "references": [] + }, + { + "id": "PTART-2024-00003", + "title": "Unrated Hit", + "body": "Some hits are not rated.", + "remediation": "They can be informational or not related to a direct attack", + "asset": "https://test.example.com", + "severity": 5, + "fix_complexity": 3, + "cvss_vector": "", + "cvss_score": "", + "added": "2024-09-06T04:22:24.707", + "labels": [ + "A09:2021-Security Logging and Monitoring Failures" + ], + "screenshots": [], + "attachments": [], + "references": [] + } + ] + } + ], + "retests": [] +} \ No newline at end of file diff --git a/unittests/scans/ptart/ptart_zero_vul.json b/unittests/scans/ptart/ptart_zero_vul.json new file mode 100644 index 00000000000..bfdf77d03a8 --- /dev/null +++ b/unittests/scans/ptart/ptart_zero_vul.json @@ -0,0 +1,26 @@ +{ + "name": "Test", + "executive_summary": "Mistakes were made", + "engagement_overview": "Things were done", + "conclusion": "Things should be put right", + "scope": "test.example.com", + "client": "Test Client", + "start_date": "2024-08-11", + "end_date": "2024-08-16", + "cvss_type": 3, + "tools": [ + "Burp Suite" + ], + "methodologies": [ + "OWASP Testing Guide V4.2" + ], + "pentesters": [ + { + "username": "hydragyrum", + "first_name": "", + "last_name": "" + } + ], + "assessments": [], + "retests": [] +} \ No newline at end of file diff --git a/unittests/tools/test_ptart_parser.py b/unittests/tools/test_ptart_parser.py new file mode 100644 index 00000000000..83be6417b3d --- /dev/null +++ b/unittests/tools/test_ptart_parser.py @@ -0,0 +1,694 @@ +from django.test import TestCase + +from dojo.models import Engagement, Product, Test +from dojo.tools.ptart.parser import PTARTParser + + +class TestPTARTParser(TestCase): + + def setUp(self): + self.product = Product(name="sample product", + description="what a description") + self.engagement = Engagement(name="sample engagement", + product=self.product) + self.test = Test(engagement=self.engagement) + + def test_ptart_parser_tools_parse_ptart_severity(self): + from dojo.tools.ptart.ptart_parser_tools import parse_ptart_severity + with self.subTest("Critical"): + self.assertEqual("Critical", parse_ptart_severity(1)) + with self.subTest("High"): + self.assertEqual("High", parse_ptart_severity(2)) + with self.subTest("Medium"): + self.assertEqual("Medium", parse_ptart_severity(3)) + with self.subTest("Low"): + self.assertEqual("Low", parse_ptart_severity(4)) + with self.subTest("Info"): + self.assertEqual("Info", parse_ptart_severity(5)) + with self.subTest("Unknown"): + self.assertEqual("Info", parse_ptart_severity(6)) + + def test_ptart_parser_tools_parse_ptart_fix_effort(self): + from dojo.tools.ptart.ptart_parser_tools import parse_ptart_fix_effort + with self.subTest("High"): + self.assertEqual("High", parse_ptart_fix_effort(1)) + with self.subTest("Medium"): + self.assertEqual("Medium", parse_ptart_fix_effort(2)) + with self.subTest("Low"): + self.assertEqual("Low", parse_ptart_fix_effort(3)) + with self.subTest("Unknown"): + self.assertEqual(None, parse_ptart_fix_effort(4)) + + def test_ptart_parser_tools_parse_title_from_hit(self): + from dojo.tools.ptart.ptart_parser_tools import parse_title_from_hit + with self.subTest("Title and ID"): + self.assertEqual("1234: Test Title", parse_title_from_hit({"title": "Test Title", "id": "1234"})) + with self.subTest("Title Only"): + self.assertEqual("Test Title", parse_title_from_hit({"title": "Test Title"})) + with self.subTest("ID Only"): + self.assertEqual("1234", parse_title_from_hit({"id": "1234"})) + with self.subTest("No Title or ID"): + self.assertEqual("Unknown Hit", parse_title_from_hit({})) + with self.subTest("Empty Title"): + self.assertEqual("Unknown Hit", parse_title_from_hit({"title": ""})) + with self.subTest("Empty ID"): + self.assertEqual("Unknown Hit", parse_title_from_hit({"id": ""})) + with self.subTest("Blank Title and Blank ID"): + self.assertEqual("Unknown Hit", parse_title_from_hit({"title": "", "id": ""})) + with self.subTest("Blank Title and Non-blank id"): + self.assertEqual("1234", parse_title_from_hit({"title": "", "id": "1234"})) + with self.subTest("Non-blank Title and Blank id"): + self.assertEqual("Test Title", parse_title_from_hit({"title": "Test Title", "id": ""})) + + def test_ptart_parser_tools_cvss_vector_acquisition(self): + from dojo.tools.ptart.ptart_parser_tools import parse_cvss_vector + with self.subTest("Test CVSSv3 Vector"): + hit = { + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + } + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", parse_cvss_vector(hit, 3)) + with self.subTest("Test CVSSv4 Vector"): + hit = { + "cvss_vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N", + } + self.assertEqual(None, parse_cvss_vector(hit, 4)) + with self.subTest("Test CVSSv3 Vector with CVSSv4 Request"): + hit = { + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + } + self.assertEqual(None, parse_cvss_vector(hit, 4)) + with self.subTest("Test CVSSv4 Vector with CVSSv3 Request"): + hit = { + "cvss_vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N", + } + self.assertEqual(None, parse_cvss_vector(hit, 3)) + with self.subTest("Test No CVSS Vector"): + hit = {} + self.assertEqual(None, parse_cvss_vector(hit, 3)) + with self.subTest("Test CVSSv2 Vector"): + hit = { + "cvss_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:C/I:C/A:C", + } + self.assertEqual(None, parse_cvss_vector(hit, 2)) + with self.subTest("Test Blank CVSS Vector"): + hit = { + "cvss_vector": "", + } + self.assertEqual(None, parse_cvss_vector(hit, 3)) + + def test_ptart_parser_tools_retest_fix_status_parse(self): + from dojo.tools.ptart.ptart_parser_tools import parse_retest_status + with self.subTest("Fixed"): + self.assertEqual("Fixed", parse_retest_status("F")) + with self.subTest("Not Fixed"): + self.assertEqual("Not Fixed", parse_retest_status("NF")) + with self.subTest("Partially Fixed"): + self.assertEqual("Partially Fixed", parse_retest_status("PF")) + with self.subTest("Not Applicable"): + self.assertEqual("Not Applicable", parse_retest_status("NA")) + with self.subTest("Not Tested"): + self.assertEqual("Not Tested", parse_retest_status("NT")) + with self.subTest("Unknown"): + self.assertEqual(None, parse_retest_status("U")) + with self.subTest("Empty"): + self.assertEqual(None, parse_retest_status("")) + + def test_ptart_parser_tools_parse_screenshots_from_hit(self): + from dojo.tools.ptart.ptart_parser_tools import parse_screenshots_from_hit + with self.subTest("No Screenshots"): + hit = {} + screenshots = parse_screenshots_from_hit(hit) + self.assertEqual([], screenshots) + with self.subTest("One Screenshot"): + hit = { + "screenshots": [{ + "caption": "One", + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }], + } + screenshots = parse_screenshots_from_hit(hit) + self.assertEqual(1, len(screenshots)) + screenshot = screenshots[0] + self.assertEqual("One.png", screenshot["title"]) + self.assertTrue(screenshot["data"] == "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + "Invalid Screenshot Data") + with self.subTest("Two Screenshots"): + hit = { + "screenshots": [{ + "caption": "One", + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }, { + "caption": "Two", + "order": 1, + "screenshot": { + "filename": "screenshots/123e4567-e89b-12d3-a456-426614174000.png", + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }], + } + screenshots = parse_screenshots_from_hit(hit) + self.assertEqual(2, len(screenshots)) + first_screenshot = screenshots[0] + self.assertEqual("One.png", first_screenshot["title"]) + self.assertTrue(first_screenshot["data"] == "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + "Invalid Screenshot Data") + second_screenshot = screenshots[1] + self.assertEqual("Two.png", second_screenshot["title"]) + self.assertTrue(second_screenshot["data"] == "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + "Invalid Screenshot Data") + with self.subTest("Empty Screenshot"): + hit = { + "screenshots": [{ + "caption": "Borked", + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "", + }, + }], + } + screenshots = parse_screenshots_from_hit(hit) + self.assertEqual(0, len(screenshots)) + with self.subTest("Screenshot with No Caption"): + hit = { + "screenshots": [{ + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }], + } + screenshots = parse_screenshots_from_hit(hit) + self.assertEqual(1, len(screenshots)) + screenshot = screenshots[0] + self.assertEqual("screenshot.png", screenshot["title"]) + self.assertTrue(screenshot["data"] == "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + "Invalid Screenshot Data") + with self.subTest("Screenshot with Blank Caption"): + hit = { + "screenshots": [{ + "caption": "", + "order": 0, + "screenshot": { + "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }], + } + screenshots = parse_screenshots_from_hit(hit) + self.assertEqual(1, len(screenshots)) + screenshot = screenshots[0] + self.assertEqual("screenshot.png", screenshot["title"]) + self.assertTrue(screenshot["data"] == "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + "Invalid Screenshot Data") + + def test_ptart_parser_tools_parse_attachment_from_hit(self): + from dojo.tools.ptart.ptart_parser_tools import parse_attachment_from_hit + with self.subTest("No Attachments"): + hit = {} + attachments = parse_attachment_from_hit(hit) + self.assertEqual([], attachments) + with self.subTest("One Attachment"): + hit = { + "attachments": [{ + "title": "License", + "data": "TUlUIExpY2Vuc2UKCkNvcHl", + }], + } + attachments = parse_attachment_from_hit(hit) + self.assertEqual(1, len(attachments)) + attachment = attachments[0] + self.assertEqual("License", attachment["title"]) + self.assertTrue(attachment["data"] == "TUlUIExpY2Vuc2UKCkNvcHl", "Invalid Attachment Data") + with self.subTest("Two Attachments"): + hit = { + "attachments": [{ + "title": "License", + "data": "TUlUIExpY2Vuc2UKCkNvcHl", + }, { + "title": "Readme", + "data": "UkVBRERtZQoK", + }], + } + attachments = parse_attachment_from_hit(hit) + self.assertEqual(2, len(attachments)) + first_attachment = attachments[0] + self.assertEqual("License", first_attachment["title"]) + self.assertTrue(first_attachment["data"] == "TUlUIExpY2Vuc2UKCkNvcHl", "Invalid Attachment Data") + second_attachment = attachments[1] + self.assertEqual("Readme", second_attachment["title"]) + self.assertTrue(second_attachment["data"] == "UkVBRERtZQoK", "Invalid Attachment Data") + with self.subTest("Empty Attachment"): + hit = { + "attachments": [{ + "title": "License", + "data": "", + }], + } + attachments = parse_attachment_from_hit(hit) + self.assertEqual(0, len(attachments)) + with self.subTest("No Data Attachment"): + hit = { + "attachments": [{ + "title": "License", + }], + } + attachments = parse_attachment_from_hit(hit) + self.assertEqual(0, len(attachments)) + with self.subTest("Attachement with no Title"): + hit = { + "attachments": [{ + "data": "TUlUIExpY2Vuc2UKCkNvcHl", + }], + } + attachments = parse_attachment_from_hit(hit) + self.assertEqual(1, len(attachments)) + attachment = attachments[0] + self.assertEqual("attachment", attachment["title"]) + self.assertTrue(attachment["data"] == "TUlUIExpY2Vuc2UKCkNvcHl", "Invalid Attachment Data") + with self.subTest("Attachment with Blank Title"): + hit = { + "attachments": [{ + "title": "", + "data": "TUlUIExpY2Vuc2UKCkNvcHl", + }], + } + attachments = parse_attachment_from_hit(hit) + self.assertEqual(1, len(attachments)) + attachment = attachments[0] + self.assertEqual("attachment", attachment["title"]) + + self.assertTrue(attachment["data"] == "TUlUIExpY2Vuc2UKCkNvcHl", "Invalid Attachment Data") + + def test_ptart_parser_tools_get_description_from_report_base(self): + from dojo.tools.ptart.ptart_parser_tools import generate_test_description_from_report + with self.subTest("No Description"): + data = {} + self.assertEqual(None, generate_test_description_from_report(data)) + with self.subTest("Description from Executive Summary Only"): + data = { + "executive_summary": "This is a summary", + } + self.assertEqual("This is a summary", generate_test_description_from_report(data)) + with self.subTest("Description from Engagement Overview Only"): + data = { + "engagement_overview": "This is an overview", + } + self.assertEqual("This is an overview", generate_test_description_from_report(data)) + with self.subTest("Description from Conclusion Only"): + data = { + "conclusion": "This is a conclusion", + } + self.assertEqual("This is a conclusion", generate_test_description_from_report(data)) + with self.subTest("Description from All Sections"): + data = { + "executive_summary": "This is a summary", + "engagement_overview": "This is an overview", + "conclusion": "This is a conclusion", + } + self.assertEqual("This is a summary\n\nThis is an overview\n\nThis is a conclusion", + generate_test_description_from_report(data)) + with self.subTest("Description from Executive Summary and Conclusion"): + data = { + "executive_summary": "This is a summary", + "conclusion": "This is a conclusion", + } + self.assertEqual("This is a summary\n\nThis is a conclusion", + generate_test_description_from_report(data)) + with self.subTest("Description from Executive Summary and Engagement Overview"): + data = { + "executive_summary": "This is a summary", + "engagement_overview": "This is an overview", + } + self.assertEqual("This is a summary\n\nThis is an overview", + generate_test_description_from_report(data)) + with self.subTest("Description from Engagement Overview and Conclusion"): + data = { + "engagement_overview": "This is an overview", + "conclusion": "This is a conclusion", + } + self.assertEqual("This is an overview\n\nThis is a conclusion", + generate_test_description_from_report(data)) + with self.subTest("Description from All Sections with Empty Strings"): + data = { + "executive_summary": "", + "engagement_overview": "", + "conclusion": "", + } + self.assertEqual(None, generate_test_description_from_report(data)) + with self.subTest("Description with Some Blank Strings"): + data = { + "executive_summary": "", + "engagement_overview": "This is an overview", + "conclusion": "", + } + self.assertEqual("This is an overview", generate_test_description_from_report(data)) + + def test_ptart_parser_tools_parse_references_from_hit(self): + from dojo.tools.ptart.ptart_parser_tools import parse_references_from_hit + with self.subTest("No References"): + hit = {} + self.assertEqual(None, parse_references_from_hit(hit)) + with self.subTest("One Reference"): + hit = { + "references": [{ + "name": "Reference", + "url": "https://ref.example.com", + }], + } + self.assertEqual("Reference: https://ref.example.com", parse_references_from_hit(hit)) + with self.subTest("Two References"): + hit = { + "references": [{ + "name": "Reference1", + "url": "https://ref.example.com", + }, { + "name": "Reference2", + "url": "https://ref2.example.com", + }], + } + self.assertEqual("Reference1: https://ref.example.com\nReference2: https://ref2.example.com", + parse_references_from_hit(hit)) + with self.subTest("No Data Reference"): + hit = { + "references": [], + } + self.assertEqual(None, parse_references_from_hit(hit)) + with self.subTest("Reference with No Name"): + hit = { + "references": [{ + "url": "https://ref.example.com", + }], + } + self.assertEqual("Reference: https://ref.example.com", parse_references_from_hit(hit)) + with self.subTest("Reference with No URL"): + hit = { + "references": [{ + "name": "Reference", + }], + } + self.assertEqual(None, parse_references_from_hit(hit)) + with self.subTest("Mixed bag of valid and invalid references"): + hit = { + "references": [{ + "name": "Reference1", + "url": "https://ref.example.com", + }, { + "name": "Reference2", + }, { + "url": "https://ref3.example.com", + }], + } + self.assertEqual("Reference1: https://ref.example.com\nReference: https://ref3.example.com", parse_references_from_hit(hit)) + + def test_ptart_parser_with_empty_json_throws_error(self): + with open("unittests/scans/ptart/empty_with_error.json", encoding="utf-8") as testfile: + parser = PTARTParser() + findings = parser.get_findings(testfile, self.test) + self.assertEqual(0, len(findings)) + + def test_ptart_parser_with_no_assessments_has_no_findings(self): + with open("unittests/scans/ptart/ptart_zero_vul.json", encoding="utf-8") as testfile: + parser = PTARTParser() + findings = parser.get_findings(testfile, self.test) + self.assertEqual(0, len(findings)) + + def test_ptart_parser_with_one_assessment_has_one_finding(self): + with open("unittests/scans/ptart/ptart_one_vul.json", encoding="utf-8") as testfile: + parser = PTARTParser() + findings = parser.get_findings(testfile, self.test) + self.assertEqual(1, len(findings)) + with self.subTest("Test Assessment: Broken Access Control"): + finding = findings[0] + self.assertEqual("PTART-2024-00002: Broken Access Control", finding.title) + self.assertEqual("High", finding.severity) + self.assertEqual( + "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + finding.description) + self.assertEqual( + "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + finding.mitigation) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", finding.cvssv3) + self.assertEqual("PTART-2024-00002", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Test Assessment", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(2, len(finding.unsaved_tags)) + self.assertEqual([ + "A01:2021-Broken Access Control", + "A04:2021-Insecure Design", + ], finding.unsaved_tags) + self.assertEqual(1, len(finding.unsaved_endpoints)) + endpoint = finding.unsaved_endpoints[0] + self.assertEqual(str(endpoint), "https://test.example.com") + self.assertEqual(2, len(finding.unsaved_files)) + screenshot = finding.unsaved_files[0] + self.assertEqual("Borked.png", screenshot["title"]) + self.assertTrue(screenshot["data"].startswith("iVBORw0KGgoAAAAN"), "Invalid Screenshot Data") + attachment = finding.unsaved_files[1] + self.assertEqual("License", attachment["title"]) + self.assertTrue(attachment["data"].startswith("TUlUIExpY2Vuc2UKCkNvcHl"), "Invalid Attachment Data") + self.assertEqual("Reference: https://ref.example.com", finding.references) + + def test_ptart_parser_with_one_assessment_has_many_findings(self): + with open("unittests/scans/ptart/ptart_many_vul.json", encoding="utf-8") as testfile: + parser = PTARTParser() + findings = parser.get_findings(testfile, self.test) + self.assertEqual(2, len(findings)) + with self.subTest("Test Assessment: Broken Access Control"): + finding = findings[0] + self.assertEqual("PTART-2024-00002: Broken Access Control", finding.title) + self.assertEqual("High", finding.severity) + self.assertEqual( + "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + finding.description) + self.assertEqual( + "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + finding.mitigation) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", finding.cvssv3) + self.assertEqual("PTART-2024-00002", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Test Assessment", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(1, len(finding.unsaved_endpoints)) + endpoint = finding.unsaved_endpoints[0] + self.assertEqual(str(endpoint), "https://test.example.com") + self.assertEqual(2, len(finding.unsaved_files)) + screenshot = finding.unsaved_files[0] + self.assertEqual("Borked.png", screenshot["title"]) + self.assertTrue(screenshot["data"].startswith("iVBORw0KGgoAAAAN"), "Invalid Screenshot Data") + attachment = finding.unsaved_files[1] + self.assertEqual("License", attachment["title"]) + self.assertTrue(attachment["data"].startswith("TUlUIExpY2Vuc2UKCkNvcHl"), "Invalid Attachment Data") + self.assertEqual(None, finding.references) + with self.subTest("Test Assessment: Unrated Hit"): + finding = findings[1] + self.assertEqual("PTART-2024-00003: Unrated Hit", finding.title) + self.assertEqual("Info", finding.severity) + self.assertEqual("Some hits are not rated.", finding.description) + self.assertEqual("They can be informational or not related to a direct attack", finding.mitigation) + self.assertEqual(None, finding.cvssv3) + self.assertEqual("PTART-2024-00003", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00003", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00003", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Test Assessment", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(None, finding.references) + + def test_ptart_parser_with_multiple_assessments_has_many_findings_correctly_grouped(self): + with open("unittests/scans/ptart/ptart_vulns_with_mult_assessments.json", encoding="utf-8") as testfile: + parser = PTARTParser() + findings = parser.get_findings(testfile, self.test) + self.assertEqual(3, len(findings)) + with self.subTest("Test Assessment: Broken Access Control"): + finding = next((f for f in findings if f.unique_id_from_tool == "PTART-2024-00002"), None) + self.assertEqual("PTART-2024-00002: Broken Access Control", finding.title) + self.assertEqual("High", finding.severity) + self.assertEqual( + "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + finding.description) + self.assertEqual( + "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + finding.mitigation) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", finding.cvssv3) + self.assertEqual("PTART-2024-00002", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Test Assessment", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(1, len(finding.unsaved_endpoints)) + endpoint = finding.unsaved_endpoints[0] + self.assertEqual(str(endpoint), "https://test.example.com") + self.assertEqual(2, len(finding.unsaved_files)) + screenshot = finding.unsaved_files[0] + self.assertEqual("Borked.png", screenshot["title"]) + self.assertTrue(screenshot["data"].startswith("iVBORw0KGgoAAAAN"), "Invalid Screenshot Data") + attachment = finding.unsaved_files[1] + self.assertEqual("License", attachment["title"]) + self.assertTrue(attachment["data"].startswith("TUlUIExpY2Vuc2UKCkNvcHl"), "Invalid Attachment Data") + self.assertEqual(None, finding.references) + with self.subTest("Test Assessment: Unrated Hit"): + finding = next((f for f in findings if f.unique_id_from_tool == "PTART-2024-00003"), None) + self.assertEqual("PTART-2024-00003: Unrated Hit", finding.title) + self.assertEqual("Info", finding.severity) + self.assertEqual("Some hits are not rated.", finding.description) + self.assertEqual("They can be informational or not related to a direct attack", finding.mitigation) + self.assertEqual(None, finding.cvssv3) + self.assertEqual("PTART-2024-00003", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00003", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00003", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Test Assessment", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(None, finding.references) + with self.subTest("New Api: HTML Injection"): + finding = next((f for f in findings if f.unique_id_from_tool == "PTART-2024-00004"), None) + self.assertEqual("PTART-2024-00004: HTML Injection", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual( + "HTML injection is a type of injection issue that occurs when a user is able to control an input point and is able to inject arbitrary HTML code into a vulnerable web page. This vulnerability can have many consequences, like disclosure of a user's session cookies that could be used to impersonate the victim, or, more generally, it can allow the attacker to modify the page content seen by the victims.", + finding.description) + self.assertEqual( + "Preventing HTML injection is trivial in some cases but can be much harder depending on the complexity of the application and the ways it handles user-controllable data.\n\nIn general, effectively preventing HTML injection vulnerabilities is likely to involve a combination of the following measures:\n\n* **Filter input on arrival**. At the point where user input is received, filter as strictly as possible based on what is expected or valid input.\n* **Encode data on output**. At the point where user-controllable data is output in HTTP responses, encode the output to prevent it from being interpreted as active content. Depending on the output context, this might require applying combinations of HTML, URL, JavaScript, and CSS encoding.", + finding.mitigation) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N", finding.cvssv3) + self.assertEqual("PTART-2024-00004", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00004", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00004", finding.cve) + self.assertEqual("Medium", finding.effort_for_fixing) + self.assertEqual("New API", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(0, len(finding.unsaved_endpoints)) + self.assertEqual(0, len(finding.unsaved_files)) + self.assertEqual(None, finding.references) + + def test_ptart_parser_with_single_vuln_on_import_test(self): + with open("unittests/scans/ptart/ptart_one_vul.json", encoding="utf-8") as testfile: + parser = PTARTParser() + tests = parser.get_tests("PTART Report", testfile) + self.assertEqual(1, len(tests)) + test = tests[0] + self.assertEqual("Test Report", test.name) + self.assertEqual("Test Report", test.type) + self.assertEqual("", test.version) + self.assertEqual("Mistakes were made\n\nThings were done\n\nThings should be put right", test.description) + self.assertEqual("2024-08-11", test.target_start.strftime("%Y-%m-%d")) + self.assertEqual("2024-08-16", test.target_end.strftime("%Y-%m-%d")) + self.assertEqual(1, len(test.findings)) + finding = test.findings[0] + self.assertEqual("PTART-2024-00002: Broken Access Control", finding.title) + self.assertEqual("High", finding.severity) + self.assertEqual( + "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + finding.description) + self.assertEqual( + "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + finding.mitigation) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", finding.cvssv3) + self.assertEqual("PTART-2024-00002", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Test Assessment", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(2, len(finding.unsaved_tags)) + self.assertEqual([ + "A01:2021-Broken Access Control", + "A04:2021-Insecure Design", + ], finding.unsaved_tags) + self.assertEqual(1, len(finding.unsaved_endpoints)) + endpoint = finding.unsaved_endpoints[0] + self.assertEqual(str(endpoint), "https://test.example.com") + self.assertEqual(2, len(finding.unsaved_files)) + screenshot = finding.unsaved_files[0] + self.assertEqual("Borked.png", screenshot["title"]) + self.assertTrue(screenshot["data"].startswith("iVBORw0KGgoAAAAN"), "Invalid Screenshot Data") + attachment = finding.unsaved_files[1] + self.assertEqual("License", attachment["title"]) + self.assertTrue(attachment["data"].startswith("TUlUIExpY2Vuc2UKCkNvcHl"), "Invalid Attachment Data") + self.assertEqual("Reference: https://ref.example.com", finding.references) + + def test_ptart_parser_with_retest_campaign(self): + with open("unittests/scans/ptart/ptart_vuln_plus_retest.json", encoding="utf-8") as testfile: + parser = PTARTParser() + findings = parser.get_findings(testfile, self.test) + self.assertEqual(3, len(findings)) + with self.subTest("Test Assessment: Broken Access Control"): + finding = next((f for f in findings if f.unique_id_from_tool == "PTART-2024-00002"), None) + self.assertEqual("PTART-2024-00002: Broken Access Control", finding.title) + self.assertEqual("High", finding.severity) + self.assertEqual( + "Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside of the limits of the user.", + finding.description) + self.assertEqual( + "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + finding.mitigation) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", finding.cvssv3) + self.assertEqual("PTART-2024-00002", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Test Assessment", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(1, len(finding.unsaved_endpoints)) + endpoint = finding.unsaved_endpoints[0] + self.assertEqual(str(endpoint), "https://test.example.com") + self.assertEqual(2, len(finding.unsaved_files)) + screenshot = finding.unsaved_files[0] + self.assertEqual("Borked.png", screenshot["title"]) + self.assertTrue(screenshot["data"].startswith("iVBORw0KGgoAAAAN"), "Invalid Screenshot Data") + attachment = finding.unsaved_files[1] + self.assertEqual("License", attachment["title"]) + self.assertTrue(attachment["data"].startswith("TUlUIExpY2Vuc2UKCkNvcHl"), "Invalid Attachment Data") + self.assertEqual(None, finding.references) + with self.subTest("Test Assessment: Unrated Hit"): + finding = next((f for f in findings if f.unique_id_from_tool == "PTART-2024-00003"), None) + self.assertEqual("PTART-2024-00003: Unrated Hit", finding.title) + self.assertEqual("Info", finding.severity) + self.assertEqual("Some hits are not rated.", finding.description) + self.assertEqual("They can be informational or not related to a direct attack", finding.mitigation) + self.assertEqual(None, finding.cvssv3) + self.assertEqual("PTART-2024-00003", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00003", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00003", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Test Assessment", finding.component_name) + self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(None, finding.references) + with self.subTest("Retest: Broken Access Control"): + finding = next((f for f in findings if f.unique_id_from_tool == "PTART-2024-00002-RT"), None) + self.assertEqual("PTART-2024-00002-RT: Broken Access Control (Not Fixed)", finding.title) + self.assertEqual("High", finding.severity) + self.assertEqual("Still borked", finding.description) + self.assertEqual( + "Access control vulnerabilities can generally be prevented by taking a defense-in-depth approach and applying the following principles:\n\n* Never rely on obfuscation alone for access control.\n* Unless a resource is intended to be publicly accessible, deny access by default.\n* Wherever possible, use a single application-wide mechanism for enforcing access controls.\n* At the code level, make it mandatory for developers to declare the access that is allowed for each resource, and deny access by default.\n* Thoroughly audit and test access controls to ensure they are working as designed.", + finding.mitigation) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", finding.cvssv3) + self.assertEqual("PTART-2024-00002-RT", finding.unique_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.vuln_id_from_tool) + self.assertEqual("PTART-2024-00002", finding.cve) + self.assertEqual("Low", finding.effort_for_fixing) + self.assertEqual("Retest: Test Retest", finding.component_name) + self.assertEqual("2024-09-08", finding.date.strftime("%Y-%m-%d")) + self.assertEqual(1, len(finding.unsaved_endpoints)) + endpoint = finding.unsaved_endpoints[0] + self.assertEqual(str(endpoint), "https://test.example.com") + self.assertEqual(1, len(finding.unsaved_files)) + screenshot = finding.unsaved_files[0] + self.assertEqual("Yet another Screenshot.png", screenshot["title"]) + self.assertTrue(screenshot["data"].startswith("iVBORw0KGgoAAAAN"), "Invalid Screenshot Data") From 2c76cda09a7e322035b6567a70ac874ddeeccc01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Oct 2024 20:47:57 -0500 Subject: [PATCH 37/74] Bump boto3 from 1.35.45 to 1.35.48 (#11132) Bumps [boto3](https://github.com/boto/boto3) from 1.35.45 to 1.35.48. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.45...1.35.48) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a70ef4f8101..e52dd84a9c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.45 # Required for Celery Broker AWS (SQS) support +boto3==1.35.48 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.3 fontawesomefree==6.6.0 From a239d8b0a36fd7cbe512190688799f1ee90152a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Oct 2024 12:46:16 -0500 Subject: [PATCH 38/74] Bump ruff from 0.7.0 to 0.7.1 (#11133) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.0 to 0.7.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.7.0...0.7.1) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index 4228b8f407e..8bf2f348238 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1 +1 @@ -ruff==0.7.0 \ No newline at end of file +ruff==0.7.1 \ No newline at end of file From c63b41ea9e69cffe3ffed4eb520827ed964dd568 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Oct 2024 12:46:36 -0500 Subject: [PATCH 39/74] Bump redis from 5.1.1 to 5.2.0 (#11134) Bumps [redis](https://github.com/redis/redis-py) from 5.1.1 to 5.2.0. - [Release notes](https://github.com/redis/redis-py/releases) - [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES) - [Commits](https://github.com/redis/redis-py/compare/v5.1.1...v5.2.0) --- updated-dependencies: - dependency-name: redis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e52dd84a9c6..812c454706a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,7 +35,7 @@ psycopg[c]==3.2.3 cryptography==43.0.3 python-dateutil==2.9.0.post0 pytz==2024.2 -redis==5.1.1 +redis==5.2.0 requests==2.32.3 sqlalchemy==2.0.36 # Required by Celery broker transport urllib3==1.26.18 From f539b1b686c42ebcf57f2cb2f4a19dfe8de15792 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 28 Oct 2024 15:27:32 +0000 Subject: [PATCH 40/74] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/package.json b/components/package.json index 4b74883bf0e..06cdce1889b 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.39.3", + "version": "2.40.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index ee1a414db8f..0dc36e95a1e 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.39.3" +__version__ = "2.40.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 cf5789792fc..3f1a22acc9d 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.39.3" +appVersion: "2.40.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.156 +version: 1.6.157-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From bc19fc321a4be13f6b100f6eac64ef4a0faad624 Mon Sep 17 00:00:00 2001 From: Ross Esposito Date: Mon, 28 Oct 2024 11:34:13 -0500 Subject: [PATCH 41/74] Fixing checksum --- dojo/settings/.settings.dist.py.sha256sum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index def3909d2c9..2a9d5c52e55 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -002b28325f11793c5aa9f09326c2d5cc66de518cce51b2cb4cb681a920b89909 +6a90a111e2b89eb2c400945c80ff76c64b135d78b84fdf6b09a6b83569946904 From 868d4e4e7f22b0f2a5d28b5792c57679d60f118c Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 28 Oct 2024 18:53:19 +0000 Subject: [PATCH 42/74] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/package.json b/components/package.json index 805b16ff7cf..06cdce1889b 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.39.4", + "version": "2.40.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index 9572cd89329..0dc36e95a1e 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.39.4" +__version__ = "2.40.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 5337b2b7059..ffac3d938b3 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.39.4" +appVersion: "2.40.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.157 +version: 1.6.158-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From e27e21e3fe05543c34c159cae70065fb44b5c4d0 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 28 Oct 2024 18:53:19 +0000 Subject: [PATCH 43/74] Update versions in application files --- components/package.json | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/package.json b/components/package.json index 805b16ff7cf..06cdce1889b 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.39.4", + "version": "2.40.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 5337b2b7059..ffac3d938b3 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.39.4" +appVersion: "2.40.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.157 +version: 1.6.158-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From 83449c76dced2501f801da38ea2868d91afda78c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:23:21 -0500 Subject: [PATCH 44/74] Bump boto3 from 1.35.48 to 1.35.49 (#11141) Bumps [boto3](https://github.com/boto/boto3) from 1.35.48 to 1.35.49. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.48...1.35.49) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 812c454706a..8e6fd1a178e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.48 # Required for Celery Broker AWS (SQS) support +boto3==1.35.49 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.3 fontawesomefree==6.6.0 From 314a9da08f31a9795716c248281740fb3406c7bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:34:58 -0500 Subject: [PATCH 45/74] Bump uwsgi from 2.0.26 to 2.0.28 (#11142) Bumps [uwsgi](https://uwsgi-docs.readthedocs.io/en/latest/) from 2.0.26 to 2.0.28. --- updated-dependencies: - dependency-name: uwsgi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8e6fd1a178e..1176e6ac573 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ redis==5.2.0 requests==2.32.3 sqlalchemy==2.0.36 # Required by Celery broker transport urllib3==1.26.18 -uWSGI==2.0.26 +uWSGI==2.0.28 vobject==0.9.8 whitenoise==5.2.0 titlecase==2.4.1 From e49d93304c451d8349288f85b3b1277744c26d70 Mon Sep 17 00:00:00 2001 From: Julien Godin <40758407+JGodin-C2C@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:50:11 +0100 Subject: [PATCH 46/74] fix: broker configuration fix for deployment (#11109) Signed-off-by: Julien Godin --- helm/defectdojo/templates/configmap.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/defectdojo/templates/configmap.yaml b/helm/defectdojo/templates/configmap.yaml index b5248a0a9ba..5ae741f0abc 100644 --- a/helm/defectdojo/templates/configmap.yaml +++ b/helm/defectdojo/templates/configmap.yaml @@ -18,7 +18,7 @@ data: DD_CELERY_BROKER_SCHEME: {{ if eq .Values.celery.broker "redis" }}{{ template "redis.scheme" . }}{{ end }} DD_CELERY_BROKER_USER: '' DD_CELERY_BROKER_HOST: {{ if eq .Values.celery.broker "redis" }}{{ template "redis.hostname" . }}{{ end }} - DD_CELERY_BROKER_PORT: '{{ if eq .Values.celery.broker "redis" }}{{ .Values.redis.master.service.ports.redis | default "6379" }}{{ end }}' + DD_CELERY_BROKER_PORT: '{{ if eq .Values.celery.broker "redis" }}{{- if ( hasKey .Values.redis "master" ) -}}{{ .Values.redis.master.service.ports.redis }}{{ else }}6379{{ end }}{{- end -}}' DD_CELERY_BROKER_PARAMS: '{{ if eq .Values.celery.broker "redis" }}{{- if .Values.redis.transportEncryption.enabled -}}{{ .Values.redis.transportEncryption.params | default "ssl_cert_reqs=optional" }}{{ end }}{{ end }}' DD_CELERY_BROKER_PATH: '{{ .Values.celery.path | default "//" }}' DD_CELERY_LOG_LEVEL: {{ .Values.celery.logLevel }} From 517f7f49ba2fe7ec4629e9bc053d21f9d5b95fe9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 19:02:02 -0500 Subject: [PATCH 47/74] Bump python-gitlab from 4.13.0 to 5.0.0 (#11140) Bumps [python-gitlab](https://github.com/python-gitlab/python-gitlab) from 4.13.0 to 5.0.0. - [Release notes](https://github.com/python-gitlab/python-gitlab/releases) - [Changelog](https://github.com/python-gitlab/python-gitlab/blob/main/CHANGELOG.md) - [Commits](https://github.com/python-gitlab/python-gitlab/compare/v4.13.0...v5.0.0) --- updated-dependencies: - dependency-name: python-gitlab dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1176e6ac573..3e560a89d61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,7 +46,7 @@ titlecase==2.4.1 social-auth-app-django==5.4.2 social-auth-core==4.5.4 gitpython==3.1.43 -python-gitlab==4.13.0 +python-gitlab==5.0.0 cpe==1.3.1 packageurl-python==0.16.0 django-crum==0.7.9 From abb773556958104ad48f499d236e0be128dd8b10 Mon Sep 17 00:00:00 2001 From: Paul Osinski <42211303+paulOsinski@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:29:33 -0400 Subject: [PATCH 48/74] Change logo for docs, add knowledge base link (#11158) * add knowledge base link * update logo for docs * add knowledge base link to master --------- Co-authored-by: Paul Osinski --- docs/assets/icons/logo.svg | 259 +++---------------------------------- docs/config.dev.toml | 6 + docs/config.master.toml | 6 + 3 files changed, 27 insertions(+), 244 deletions(-) diff --git a/docs/assets/icons/logo.svg b/docs/assets/icons/logo.svg index 71a24baac76..75983a52716 100644 --- a/docs/assets/icons/logo.svg +++ b/docs/assets/icons/logo.svg @@ -1,244 +1,15 @@ - - - - - - - - image/svg+xml - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/docs/config.dev.toml b/docs/config.dev.toml index 65fff4564ba..de3d1b24c36 100644 --- a/docs/config.dev.toml +++ b/docs/config.dev.toml @@ -77,6 +77,12 @@ weight = 1 pre = "" url = "https://github.com/DefectDojo/django-DefectDojo" +[[menu.main]] + name = "Knowledge Base" + weight = 50 + pre = "" + url = "https://support.defectdojo.com" + [markup] [markup.goldmark] [markup.goldmark.renderer] diff --git a/docs/config.master.toml b/docs/config.master.toml index 29c4e0a6adc..22f2f7748ab 100644 --- a/docs/config.master.toml +++ b/docs/config.master.toml @@ -77,6 +77,12 @@ weight = 1 pre = "" url = "https://github.com/DefectDojo/django-DefectDojo" +[[menu.main]] + name = "Knowledge Base" + weight = 50 + pre = "" + url = "https://support.defectdojo.com" + [markup] [markup.goldmark] [markup.goldmark.renderer] From da2df33d47eb5a30bb26d0575e9b3d44a6b4a9df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 20:29:11 -0500 Subject: [PATCH 49/74] Bump boto3 from 1.35.49 to 1.35.50 (#11155) Bumps [boto3](https://github.com/boto/boto3) from 1.35.49 to 1.35.50. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.49...1.35.50) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3e560a89d61..8c2b093b748 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.49 # Required for Celery Broker AWS (SQS) support +boto3==1.35.50 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.3 fontawesomefree==6.6.0 From 518846c7597af774337e71d90a44fc78bae947a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:58:00 -0500 Subject: [PATCH 50/74] Bump bleach from 6.1.0 to 6.2.0 (#11161) Bumps [bleach](https://github.com/mozilla/bleach) from 6.1.0 to 6.2.0. - [Changelog](https://github.com/mozilla/bleach/blob/main/CHANGES) - [Commits](https://github.com/mozilla/bleach/compare/v6.1.0...v6.2.0) --- updated-dependencies: - dependency-name: bleach dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8c2b093b748..3626732dc7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # requirements.txt for DefectDojo using Python 3.x asteval==1.0.5 -bleach==6.1.0 +bleach==6.2.0 bleach[css] celery==5.4.0 defusedxml==0.7.1 From fc936234b17fcfbebce99b543d6170fefc4719f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:58:44 -0500 Subject: [PATCH 51/74] Bump boto3 from 1.35.50 to 1.35.51 (#11160) Bumps [boto3](https://github.com/boto/boto3) from 1.35.50 to 1.35.51. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.50...1.35.51) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3626732dc7d..df6b56730d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.50 # Required for Celery Broker AWS (SQS) support +boto3==1.35.51 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.3 fontawesomefree==6.6.0 From 3368c82c4ef77fb11cdd2dd805a9deec556b1ba1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:25:47 -0500 Subject: [PATCH 52/74] Update Helm release postgresql from 16.0.6 to ~16.1.0 (helm/defectdojo/Chart.yaml) (#11164) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- helm/defectdojo/Chart.lock | 6 +++--- helm/defectdojo/Chart.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helm/defectdojo/Chart.lock b/helm/defectdojo/Chart.lock index 49d15928ea4..611d1100a44 100644 --- a/helm/defectdojo/Chart.lock +++ b/helm/defectdojo/Chart.lock @@ -1,12 +1,12 @@ dependencies: - name: postgresql repository: https://charts.bitnami.com/bitnami - version: 16.0.0 + version: 16.1.0 - name: postgresql-ha repository: https://charts.bitnami.com/bitnami version: 9.4.11 - name: redis repository: https://charts.bitnami.com/bitnami version: 19.6.4 -digest: sha256:43166002555f6bdaac719d3d54e56a3e069b17ed29acd1c70951b7b99b102ae7 -generated: "2024-10-02T16:37:38.736091938Z" +digest: sha256:499d18e7070e7752e0dccfa2187d755570e105eb21cae37d6f0623a333997db8 +generated: "2024-10-30T17:58:45.866148081Z" diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index ffac3d938b3..af7a77d7b50 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -10,7 +10,7 @@ maintainers: url: https://github.com/DefectDojo/django-DefectDojo dependencies: - name: postgresql - version: ~16.0.0 + version: ~16.1.0 repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabled - name: postgresql-ha From ae9718ff90f993fb1cac1f9de9f4f75c06a764b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:25:29 -0500 Subject: [PATCH 53/74] Bump jquery-ui from 1.14.0 to 1.14.1 in /components (#11170) Bumps [jquery-ui](https://github.com/jquery/jquery-ui) from 1.14.0 to 1.14.1. - [Release notes](https://github.com/jquery/jquery-ui/releases) - [Commits](https://github.com/jquery/jquery-ui/compare/1.14.0...1.14.1) --- updated-dependencies: - dependency-name: jquery-ui dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- components/package.json | 2 +- components/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/package.json b/components/package.json index 7d7c9d1b857..74a5293b38a 100644 --- a/components/package.json +++ b/components/package.json @@ -26,7 +26,7 @@ "google-code-prettify": "^1.0.0", "jquery": "^3.7.1", "jquery-highlight": "3.5.0", - "jquery-ui": "1.14.0", + "jquery-ui": "1.14.1", "jquery.cookie": "1.4.1", "jquery.flot.tooltip": "^0.9.0", "jquery.hotkeys": "jeresig/jquery.hotkeys#master", diff --git a/components/yarn.lock b/components/yarn.lock index 952d09ff22a..7f7ddd04d73 100644 --- a/components/yarn.lock +++ b/components/yarn.lock @@ -678,10 +678,10 @@ jquery-highlight@3.5.0: dependencies: jquery ">= 1.0.0" -jquery-ui@1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.14.0.tgz#b75d417826f0bab38125f907356d2e3313a9c6d5" - integrity sha512-mPfYKBoRCf0MzaT2cyW5i3IuZ7PfTITaasO5OFLAQxrHuI+ZxruPa+4/K1OMNT8oElLWGtIxc9aRbyw20BKr8g== +jquery-ui@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.14.1.tgz#ba342ea3ffff662b787595391f607d923313e040" + integrity sha512-DhzsYH8VeIvOaxwi+B/2BCsFFT5EGjShdzOcm5DssWjtcpGWIMsn66rJciDA6jBruzNiLf1q0KvwMoX1uGNvnQ== dependencies: jquery ">=1.12.0 <5.0.0" From f401370cf0fae5a1d3b16eb4a0054d8b8d04f742 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:25:59 -0500 Subject: [PATCH 54/74] Bump boto3 from 1.35.51 to 1.35.52 (#11171) Bumps [boto3](https://github.com/boto/boto3) from 1.35.51 to 1.35.52. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.51...1.35.52) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index df6b56730d3..4bee0795764 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.51 # Required for Celery Broker AWS (SQS) support +boto3==1.35.52 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.3 fontawesomefree==6.6.0 From bf7ca81d107302c3c6e2d6d128197ec3cf5f4be2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:52:58 -0500 Subject: [PATCH 55/74] Bump cvss from 3.2 to 3.3 (#11177) Bumps [cvss](https://github.com/RedHatProductSecurity/cvss) from 3.2 to 3.3. - [Release notes](https://github.com/RedHatProductSecurity/cvss/releases) - [Commits](https://github.com/RedHatProductSecurity/cvss/compare/v3.2...v3.3) --- updated-dependencies: - dependency-name: cvss dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4bee0795764..d5134c5cd0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -58,7 +58,7 @@ vcrpy==6.0.2 vcrpy-unittest==0.1.7 django-tagulous==2.1.0 PyJWT==2.9.0 -cvss==3.2 +cvss==3.3 django-fieldsignals==0.7.0 hyperlink==21.0.0 django-test-migrations==1.4.0 From abf8f698aab832c5c7612f1b0519a59b229ef772 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:53:17 -0500 Subject: [PATCH 56/74] Bump drf-spectacular-sidecar from 2024.7.1 to 2024.11.1 (#11176) Bumps [drf-spectacular-sidecar](https://github.com/tfranzel/drf-spectacular-sidecar) from 2024.7.1 to 2024.11.1. - [Commits](https://github.com/tfranzel/drf-spectacular-sidecar/compare/2024.7.1...2024.11.1) --- updated-dependencies: - dependency-name: drf-spectacular-sidecar dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d5134c5cd0d..bd195e7befd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -64,7 +64,7 @@ hyperlink==21.0.0 django-test-migrations==1.4.0 djangosaml2==1.9.3 drf-spectacular==0.27.2 -drf-spectacular-sidecar==2024.7.1 +drf-spectacular-sidecar==2024.11.1 django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 From eb66211f6bb899f0efe8fb0adf795b95b5cd0c14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:53:33 -0500 Subject: [PATCH 57/74] Bump boto3 from 1.35.52 to 1.35.53 (#11175) Bumps [boto3](https://github.com/boto/boto3) from 1.35.52 to 1.35.53. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.35.52...1.35.53) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bd195e7befd..949f2e5793d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,7 +69,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.3 pycurl==7.45.3 # Required for Celery Broker AWS (SQS) support -boto3==1.35.52 # Required for Celery Broker AWS (SQS) support +boto3==1.35.53 # Required for Celery Broker AWS (SQS) support netaddr==1.3.0 vulners==2.2.3 fontawesomefree==6.6.0 From 7c75f6165dd6225ef8cc841efed83eb3ad33c263 Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:04:12 +0100 Subject: [PATCH 58/74] Ruff: Add and fix D403 (#11063) --- dojo/utils.py | 2 +- ruff.toml | 2 +- unittests/test_apiv2_notifications.py | 2 +- unittests/tools/test_github_vulnerability_parser.py | 8 ++++---- unittests/tools/test_sarif_parser.py | 2 +- unittests/tools/test_sonarqube_parser.py | 2 +- unittests/tools/test_tenable_parser.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dojo/utils.py b/dojo/utils.py index 65d6bdf728c..0e340f2ce01 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -2222,7 +2222,7 @@ def mass_model_updater(model_type, models, function, fields, page_size=1000, ord def to_str_typed(obj): - """for code that handles multiple types of objects, print not only __str__ but prefix the type of the object""" + """For code that handles multiple types of objects, print not only __str__ but prefix the type of the object""" return f"{type(obj)}: {obj}" diff --git a/ruff.toml b/ruff.toml index 25e456a5488..ac0f0ece1ed 100644 --- a/ruff.toml +++ b/ruff.toml @@ -37,7 +37,7 @@ select = [ "W", "C90", "I", - "D2", "D3", + "D2", "D3", "D403", "UP", "YTT", "ASYNC", diff --git a/unittests/test_apiv2_notifications.py b/unittests/test_apiv2_notifications.py index d24a05f596e..09d57dfd9fe 100644 --- a/unittests/test_apiv2_notifications.py +++ b/unittests/test_apiv2_notifications.py @@ -57,7 +57,7 @@ def test_notification_template_multiple(self): self.assertEqual("Notification template already exists", r.json()["non_field_errors"][0]) def test_user_notifications(self): - """creates user and checks if template is assigned""" + """Creates user and checks if template is assigned""" user = {"user": self.create_test_user()} r = self.client.get(reverse("notifications-list"), user, format="json") self.assertEqual(r.status_code, 200) diff --git a/unittests/tools/test_github_vulnerability_parser.py b/unittests/tools/test_github_vulnerability_parser.py index c0c9a0350ec..00321647bf1 100644 --- a/unittests/tools/test_github_vulnerability_parser.py +++ b/unittests/tools/test_github_vulnerability_parser.py @@ -9,14 +9,14 @@ class TestGithubVulnerabilityParser(DojoTestCase): def test_parse_file_with_no_vuln_has_no_findings(self): - """sample with zero vulnerability""" + """Sample with zero vulnerability""" with open("unittests/scans/github_vulnerability/github-0-vuln.json", encoding="utf-8") as testfile: parser = GithubVulnerabilityParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(0, len(findings)) def test_parse_file_with_one_vuln_has_one_findings(self): - """sample with one vulnerability""" + """Sample with one vulnerability""" with open("unittests/scans/github_vulnerability/github-1-vuln.json", encoding="utf-8") as testfile: parser = GithubVulnerabilityParser() findings = parser.get_findings(testfile, Test()) @@ -36,7 +36,7 @@ def test_parse_file_with_one_vuln_has_one_findings(self): self.assertEqual(finding.unique_id_from_tool, "aabbccddeeff1122334401") def test_parse_file_with_one_vuln_has_one_finding_and_dependabot_direct_link(self): - """sample with one vulnerability""" + """Sample with one vulnerability""" with open("unittests/scans/github_vulnerability/github-1-vuln-repo-dependabot-link.json", encoding="utf-8") as testfile: parser = GithubVulnerabilityParser() findings = parser.get_findings(testfile, Test()) @@ -56,7 +56,7 @@ def test_parse_file_with_one_vuln_has_one_finding_and_dependabot_direct_link(sel self.assertEqual(finding.unique_id_from_tool, "aabbccddeeff1122334401") def test_parse_file_with_multiple_vuln_has_multiple_findings(self): - """sample with five vulnerability""" + """Sample with five vulnerability""" with open("unittests/scans/github_vulnerability/github-5-vuln.json", encoding="utf-8") as testfile: parser = GithubVulnerabilityParser() findings = parser.get_findings(testfile, Test()) diff --git a/unittests/tools/test_sarif_parser.py b/unittests/tools/test_sarif_parser.py index 0ae50e659f2..aafa91d09e1 100644 --- a/unittests/tools/test_sarif_parser.py +++ b/unittests/tools/test_sarif_parser.py @@ -28,7 +28,7 @@ def test_example_report(self): self.common_checks(finding) def test_suppression_report(self): - """test report file having different suppression definitions""" + """Test report file having different suppression definitions""" with open(path.join(path.dirname(__file__), "../scans/sarif/suppression_test.sarif"), encoding="utf-8") as testfile: parser = SarifParser() findings = parser.get_findings(testfile, Test()) diff --git a/unittests/tools/test_sonarqube_parser.py b/unittests/tools/test_sonarqube_parser.py index 16b80aa9eb1..ef4912510b0 100644 --- a/unittests/tools/test_sonarqube_parser.py +++ b/unittests/tools/test_sonarqube_parser.py @@ -245,7 +245,7 @@ def test_detailed_parse_file_with_table_in_table(self): my_file_handle.close() def test_detailed_parse_file_with_rule_undefined(self): - """the vulnerability's rule is not in the list of rules""" + """The vulnerability's rule is not in the list of rules""" my_file_handle, _product, _engagement, test = self.init( get_unit_tests_path() + "/scans/sonarqube/sonar-rule-undefined.html", ) diff --git a/unittests/tools/test_tenable_parser.py b/unittests/tools/test_tenable_parser.py index 7be782c49e3..cde5119fe9a 100644 --- a/unittests/tools/test_tenable_parser.py +++ b/unittests/tools/test_tenable_parser.py @@ -155,7 +155,7 @@ def test_parse_some_findings_samples_nessus_legacy(self): self.assertEqual("CVE-2005-1794", vulnerability_id) def test_parse_some_findings_with_cvssv3_nessus_legacy(self): - """test with cvssv3""" + """Test with cvssv3""" with open(path.join(path.dirname(__file__), "../scans/tenable/nessus/nessus_with_cvssv3.nessus"), encoding="utf-8") as testfile: parser = TenableParser() findings = parser.get_findings(testfile, self.create_test()) From ab2b88ef5b20b87ac99c19257f72a2561c5bc7a8 Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Fri, 1 Nov 2024 19:11:29 +0100 Subject: [PATCH 59/74] Ruff: Add and fix S101 (#11066) --- dojo/middleware.py | 16 ++++++---- ruff.toml | 2 +- tests/Import_scanner_test.py | 8 ++--- tests/notifications_test.py | 41 +++++++++++++------------- unittests/test_rest_framework.py | 50 ++++++++++++++++++-------------- 5 files changed, 63 insertions(+), 54 deletions(-) diff --git a/dojo/middleware.py b/dojo/middleware.py index 29eff445b34..9fcb8a51dbc 100644 --- a/dojo/middleware.py +++ b/dojo/middleware.py @@ -35,12 +35,16 @@ def __init__(self, get_response): self.get_response = get_response def __call__(self, request): - assert hasattr(request, "user"), "The Login Required middleware\ - requires authentication middleware to be installed. Edit your\ - MIDDLEWARE_CLASSES setting to insert\ - 'django.contrib.auth.middleware.AuthenticationMiddleware'. If that doesn't\ - work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\ - 'django.core.context_processors.auth'." + if not hasattr(request, "user"): + msg = ( + "The Login Required middleware " + "requires authentication middleware to be installed. Edit your " + "MIDDLEWARE_CLASSES setting to insert " + "'django.contrib.auth.middleware.AuthenticationMiddleware'. If that doesn't " + "work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes " + "'django.core.context_processors.auth'." + ) + raise AttributeError(msg) if not request.user.is_authenticated: path = request.path_info.lstrip("/") if not any(m.match(path) for m in EXEMPT_URLS): diff --git a/ruff.toml b/ruff.toml index ac0f0ece1ed..0f3ede2f2c2 100644 --- a/ruff.toml +++ b/ruff.toml @@ -41,7 +41,7 @@ select = [ "UP", "YTT", "ASYNC", - "S2", "S5", "S7", + "S101", "S2", "S5", "S7", "FBT001", "FBT003", "A003", "A004", "A006", "COM", diff --git a/tests/Import_scanner_test.py b/tests/Import_scanner_test.py index b1597e95833..3006393aec3 100644 --- a/tests/Import_scanner_test.py +++ b/tests/Import_scanner_test.py @@ -54,7 +54,7 @@ def test_check_test_file(self): logger.info("https://github.com/DefectDojo/sample-scan-files\n") for test in missing_tests: logger.info(test) - assert len(missing_tests) == 0 + self.assertEqual(len(missing_tests), 0) def test_check_for_forms(self): forms_path = dir_path[:-5] + "dojo/forms.py" @@ -91,7 +91,7 @@ def test_check_for_forms(self): logger.info("https://github.com/DefectDojo/django-DefectDojo/blob/master/dojo/forms.py\n") for tool in missing_forms: logger.info(tool) - assert len(missing_forms) == 0 + self.assertEqual(len(missing_forms), 0) @unittest.skip("Deprecated since Dynamic Parser infrastructure") def test_check_for_options(self): @@ -131,7 +131,7 @@ def test_check_for_options(self): logger.info("https://github.com/DefectDojo/django-DefectDojo/blob/master/dojo/templates/dojo/import_scan_results.html\n") for tool in missing_templates: logger.info(tool) - assert len(missing_templates) == 0 + self.assertEqual(len(missing_templates), 0) def test_engagement_import_scan_result(self): driver = self.driver @@ -216,7 +216,7 @@ def test_engagement_import_scan_result(self): logger.info("https://github.com/DefectDojo/sample-scan-files\n") for test in failed_tests: logger.info(test) - assert len(failed_tests) == 0 + self.assertEqual(len(failed_tests), 0) def tearDown(self): super().tearDown(self) diff --git a/tests/notifications_test.py b/tests/notifications_test.py index e64527cec9c..d71067ce68b 100644 --- a/tests/notifications_test.py +++ b/tests/notifications_test.py @@ -41,11 +41,13 @@ def test_disable_personal_notification(self): self.disable_notification() driver.get(self.base_url + "notifications") + in_place = False try: driver.find_element(By.XPATH, f"//input[@name='product_added' and @value='{self.type}']") - assert False + in_place = True except NoSuchElementException: - assert True + in_place = False + self.assertFalse(in_place) def test_enable_personal_notification(self): # Login to the site. Password will have to be modified @@ -56,13 +58,9 @@ def test_enable_personal_notification(self): driver.get(self.base_url + "notifications") try: driver.find_element(By.XPATH, f"//input[@name='product_added' and @value='{self.type}']") - assert True except NoSuchElementException: - if self.type == "msteams": - # msteam should be not in personal notifications - assert True - else: - assert False + # msteam should be not in personal notifications + self.assertEqual(self.type, "msteams") def test_disable_system_notification(self): # Login to the site. Password will have to be modified @@ -72,11 +70,13 @@ def test_disable_system_notification(self): self.disable_notification() driver.get(self.base_url + "notifications/system") + in_place = False try: driver.find_element(By.XPATH, f"//input[@name='product_added' and @value='{self.type}']") - assert False + in_place = True except NoSuchElementException: - assert True + in_place = False + self.assertFalse(in_place) def test_enable_system_notification(self): # Login to the site. Password will have to be modified @@ -84,12 +84,13 @@ def test_enable_system_notification(self): driver = self.driver self.enable_notification() - driver.get(self.base_url + "notifications/system") + in_place = False try: driver.find_element(By.XPATH, f"//input[@name='product_added' and @value='{self.type}']") - assert True + in_place = True except NoSuchElementException: - assert False + in_place = False + self.assertFalse(in_place) def test_disable_template_notification(self): # Login to the site. Password will have to be modified @@ -99,11 +100,13 @@ def test_disable_template_notification(self): self.disable_notification() driver.get(self.base_url + "notifications/template") + in_place = False try: driver.find_element(By.XPATH, f"//input[@name='product_added' and @value='{self.type}']") - assert False + in_place = True except NoSuchElementException: - assert True + in_place = False + self.assertFalse(in_place) def test_enable_template_notification(self): # Login to the site. Password will have to be modified @@ -114,13 +117,9 @@ def test_enable_template_notification(self): driver.get(self.base_url + "notifications/template") try: driver.find_element(By.XPATH, f"//input[@name='product_added' and @value='{self.type}']") - assert True except NoSuchElementException: - if self.type == "msteams": - # msteam should be not in personal notifications - assert True - else: - assert False + # msteam should be not in personal notifications + self.assertEqual(self.type, "msteams") def test_user_mail_notifications_change(self): # Login to the site. Password will have to be modified diff --git a/unittests/test_rest_framework.py b/unittests/test_rest_framework.py index 4021efc121a..8b2c75e7629 100644 --- a/unittests/test_rest_framework.py +++ b/unittests/test_rest_framework.py @@ -342,7 +342,8 @@ def _check(schema, obj): self._errors = [] self._prefix = [] _check(schema, obj) - assert not self._has_failed, "\n" + "\n".join(self._errors) + "\nFailed with " + str(len(self._errors)) + " errors" + if self._has_failed: + raise AssertionError("\n" + "\n".join(self._errors) + "\nFailed with " + str(len(self._errors)) + " errors") class TestType(Enum): @@ -1207,15 +1208,15 @@ def test_duplicate(self): result = self.client.get(self.url + "2/") self.assertEqual(result.status_code, status.HTTP_200_OK, "Could not check new duplicate") result_json = result.json() - assert result_json["duplicate"] - assert result_json["duplicate_finding"] == 3 + self.assertTrue(result_json["duplicate"]) + self.assertEqual(result_json["duplicate_finding"], 3) # Check duplicate status result = self.client.get(self.url + "3/duplicate/") - assert result.status_code == status.HTTP_200_OK, "Could not check duplicate status" + self.assertEqual(result.status_code, status.HTTP_200_OK, "Could not check duplicate status") result_json = result.json() # Should return all duplicates for id=3 - assert set(x["id"] for x in result_json) == {2, 4, 5, 6} # noqa: C401 + self.assertEqual({x["id"] for x in result_json}, {2, 4, 5, 6}) # Reset duplicate result = self.client.post(self.url + "2/duplicate/reset/") @@ -1223,44 +1224,44 @@ def test_duplicate(self): new_result = self.client.get(self.url + "2/") self.assertEqual(result.status_code, status.HTTP_204_NO_CONTENT, "Could not check reset duplicate status") result_json = new_result.json() - assert not result_json["duplicate"] - assert result_json["duplicate_finding"] is None + self.assertFalse(result_json["duplicate"]) + self.assertIsNone(result_json["duplicate_finding"]) def test_filter_steps_to_reproduce(self): # Confirm initial data result = self.client.get(self.url + "?steps_to_reproduce=lorem") self.assertEqual(result.status_code, status.HTTP_200_OK, "Could not filter on steps_to_reproduce") result_json = result.json() - assert result_json["count"] == 0 + self.assertEqual(result_json["count"], 0) # Set steps to reproduce result = self.client.patch(self.url + "2/", data={"steps_to_reproduce": "Lorem ipsum dolor sit amet"}) self.assertEqual(result.status_code, status.HTTP_200_OK, "Could not patch finding with steps to reproduce") - assert result.json()["steps_to_reproduce"] == "Lorem ipsum dolor sit amet" + self.assertEqual(result.json()["steps_to_reproduce"], "Lorem ipsum dolor sit amet") result = self.client.patch(self.url + "3/", data={"steps_to_reproduce": "Ut enim ad minim veniam"}) self.assertEqual(result.status_code, status.HTTP_200_OK, "Could not patch finding with steps to reproduce") - assert result.json()["steps_to_reproduce"] == "Ut enim ad minim veniam" + self.assertEqual(result.json()["steps_to_reproduce"], "Ut enim ad minim veniam") # Test result = self.client.get(self.url + "?steps_to_reproduce=lorem") self.assertEqual(result.status_code, status.HTTP_200_OK, "Could not filter on steps_to_reproduce") result_json = result.json() - assert result_json["count"] == 1 - assert result_json["results"][0]["id"] == 2 - assert result_json["results"][0]["steps_to_reproduce"] == "Lorem ipsum dolor sit amet" + self.assertEqual(result_json["count"], 1) + self.assertEqual(result_json["results"][0]["id"], 2) + self.assertEqual(result_json["results"][0]["steps_to_reproduce"], "Lorem ipsum dolor sit amet") # Set steps to reproduce result = self.client.patch(self.url + "2/", data={"steps_to_reproduce": ""}) self.assertEqual(result.status_code, status.HTTP_200_OK, "Could not patch finding with steps to reproduce") - assert result.json()["steps_to_reproduce"] == "" + self.assertEqual(result.json()["steps_to_reproduce"], "") result = self.client.patch(self.url + "3/", data={"steps_to_reproduce": ""}) self.assertEqual(result.status_code, status.HTTP_200_OK, "Could not patch finding with steps to reproduce") - assert result.json()["steps_to_reproduce"] == "" + self.assertEqual(result.json()["steps_to_reproduce"], "") def test_severity_validation(self): result = self.client.patch(self.url + "2/", data={"severity": "Not a valid choice"}) self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST, "Severity just got set to something invalid") - assert result.json()["severity"] == ["Severity must be one of the following: ['Info', 'Low', 'Medium', 'High', 'Critical']"] + self.assertEqual(result.json()["severity"], ["Severity must be one of the following: ['Info', 'Low', 'Medium', 'High', 'Critical']"]) class FindingMetadataTest(BaseClass.BaseClassTest): @@ -1295,33 +1296,38 @@ def test_create(self): self.assertEqual(200, response.status_code, response.data) results = self.client.get(self.base_url).data + correct = False for result in results: if result["name"] == "test_meta2" and result["value"] == "40": + correct = True return - assert False, "Metadata was not created correctly" + self.assertTrue(correct, "Metadata was not created correctly") def test_create_duplicate(self): result = self.client.post(self.base_url, data={"name": "test_meta", "value": "40"}) - assert result.status_code == status.HTTP_400_BAD_REQUEST, "Metadata creation did not failed on duplicate" + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST, "Metadata creation did not failed on duplicate") def test_get(self): results = self.client.get(self.base_url, format="json").data + correct = False for result in results: if result["name"] == "test_meta" and result["value"] == "20": + correct = True return - assert False, "Metadata was not created correctly" + self.assertTrue(correct, "Metadata was not created correctly") def test_update(self): self.client.put(self.base_url + "?name=test_meta", data={"name": "test_meta", "value": "40"}) result = self.client.get(self.base_url).data[0] - assert result["name"] == "test_meta" and result["value"] == "40", "Metadata not edited correctly" + self.assertEqual(result["name"], "test_meta", "Metadata not edited correctly") + self.assertEqual(result["value"], "40", "Metadata not edited correctly") def test_delete(self): self.client.delete(self.base_url + "?name=test_meta") result = self.client.get(self.base_url).data - assert len(result) == 0, "Metadata not deleted correctly" + self.assertEqual(len(result), 0, "Metadata not deleted correctly") class FindingTemplatesTest(BaseClass.BaseClassTest): @@ -1543,7 +1549,7 @@ def __init__(self, *args, **kwargs): def test_severity_validation(self): result = self.client.patch(self.url + "2/", data={"severity": "Not a valid choice"}) self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST, "Severity just got set to something invalid") - assert result.json()["severity"] == ["Severity must be one of the following: ['Info', 'Low', 'Medium', 'High', 'Critical']"] + self.assertEqual(result.json()["severity"], ["Severity must be one of the following: ['Info', 'Low', 'Medium', 'High', 'Critical']"]) class TestsTest(BaseClass.BaseClassTest): From 5a80eee057a924b1995628b125a87c4f922529ba Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 1 Nov 2024 19:15:05 +0100 Subject: [PATCH 60/74] Add ELBA vulnerability URL (#11138) --- dojo/settings/.settings.dist.py.sha256sum | 2 +- dojo/settings/settings.dist.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index def3909d2c9..79cb4cfb2ea 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -002b28325f11793c5aa9f09326c2d5cc66de518cce51b2cb4cb681a920b89909 +d738d221fdbc0b2e04d9ff13d79c6cba52c49adfb81243a278252592a412d246 diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 846639f07a3..9a8f28b2ddc 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1736,6 +1736,7 @@ def saml2_attrib_map_format(dict): "USN": "https://ubuntu.com/security/notices/", # e.g. https://ubuntu.com/security/notices/USN-6642-1 "DLA": "https://security-tracker.debian.org/tracker/", # e.g. https://security-tracker.debian.org/tracker/DLA-3917-1 "ELSA": "https://linux.oracle.com/errata/&&.html", # e.g. https://linux.oracle.com/errata/ELSA-2024-12714.html + "ELBA": "https://linux.oracle.com/errata/&&.html", # e.g. https://linux.oracle.com/errata/ELBA-2024-7457.html "RXSA": "https://errata.rockylinux.org/", # e.g. https://errata.rockylinux.org/RXSA-2024:4928 } # List of acceptable file types that can be uploaded to a given object via arbitrary file upload From 2d27077b0b2193238f8bc6a3d9dd3ed5623bbe0d Mon Sep 17 00:00:00 2001 From: Charles Neill <1749665+cneill@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:18:41 -0500 Subject: [PATCH 61/74] Always populating `description`, adding `title` for webhook payloads (#11159) * Adding some information to webhook payloads * Updating webhook docs * Fixing unit tests * Renaming 'event_title' to 'title' --- .../notification_webhooks/_index.md | 16 +++++++-------- .../notification_webhooks/engagement_added.md | 5 +++-- .../notification_webhooks/product_added.md | 5 +++-- .../product_type_added.md | 5 +++-- .../notification_webhooks/scan_added.md | 5 +++-- .../notification_webhooks/test_added.md | 5 +++-- dojo/notifications/helper.py | 7 +++++++ .../webhooks/subtemplates/base.tpl | 3 ++- unittests/test_notifications.py | 20 ++++++++++++++----- 9 files changed, 47 insertions(+), 24 deletions(-) diff --git a/docs/content/en/integrations/notification_webhooks/_index.md b/docs/content/en/integrations/notification_webhooks/_index.md index d8fe606cffa..cbe9294041e 100644 --- a/docs/content/en/integrations/notification_webhooks/_index.md +++ b/docs/content/en/integrations/notification_webhooks/_index.md @@ -5,11 +5,11 @@ weight: 7 chapter: true --- -Webhooks are HTTP requests coming from the DefectDojo instance towards user-defined webserver which expects this kind of incoming traffic. +Webhooks are HTTP requests coming from the DefectDojo instance towards a user-defined webserver which expects this kind of incoming traffic. ## Transition graph: -It is not unusual that in some cases webhook can not be performed. It is usually connected to network issues, server misconfiguration, or running upgrades on the server. DefectDojo needs to react to these outages. It might temporarily or permanently disable related endpoints. The following graph shows how it might change the status of the webhook definition based on HTTP responses (or manual user interaction). +It is not unusual that in some cases a webhook can not be delivered. It is usually connected to network issues, server misconfiguration, or running upgrades on the server. DefectDojo needs to react to these outages. It might temporarily or permanently disable related endpoints. The following graph shows how it might change the status of the webhook definition based on HTTP responses (or manual user interaction). ```mermaid flowchart TD @@ -53,7 +53,7 @@ Notes: The body of each request is JSON which contains data about related events like names and IDs of affected elements. Examples of bodies are on pages related to each event (see below). -Each request contains the following headers. They might be useful for better handling of events by server this process events. +Each request contains the following headers. They might be useful for better handling of events by the server receiving them. ```yaml User-Agent: DefectDojo- @@ -62,18 +62,18 @@ X-DefectDojo-Instance: ``` ## Disclaimer -This functionality is new and in experimental mode. This means Functionality might generate breaking changes in following DefectDojo releases and might not be considered final. +This functionality is new and in experimental mode. This means functionality might generate breaking changes in following DefectDojo releases and might not be considered final. -However, the community is open to feedback to make this functionality better and transform it stable as soon as possible. +However, the community is open to feedback to make this functionality better and get it stable as soon as possible. ## Roadmap -There are a couple of known issues that are expected to be implemented as soon as core functionality is considered ready. +There are a couple of known issues that are expected to be resolved as soon as core functionality is considered ready. - Support events - Not only adding products, product types, engagements, tests, or upload of new scans but also events around SLA -- User webhook - right now only admins can define webhooks; in the future also users will be able to define their own +- User webhook - right now only admins can define webhooks; in the future, users will also be able to define their own - Improvement in UI - add filtering and pagination of webhook endpoints ## Events - \ No newline at end of file + diff --git a/docs/content/en/integrations/notification_webhooks/engagement_added.md b/docs/content/en/integrations/notification_webhooks/engagement_added.md index 64fd7746ec2..36e31586a50 100644 --- a/docs/content/en/integrations/notification_webhooks/engagement_added.md +++ b/docs/content/en/integrations/notification_webhooks/engagement_added.md @@ -12,7 +12,8 @@ X-DefectDojo-Event: engagement_added ## Event HTTP body ```json { - "description": null, + "description": "", + "title": "", "engagement": { "id": 7, "name": "notif eng", @@ -35,4 +36,4 @@ X-DefectDojo-Event: engagement_added "url_ui": "http://localhost:8080/engagement/7", "user": null } -``` \ No newline at end of file +``` diff --git a/docs/content/en/integrations/notification_webhooks/product_added.md b/docs/content/en/integrations/notification_webhooks/product_added.md index 2d90a6a681f..dea3cd27f2a 100644 --- a/docs/content/en/integrations/notification_webhooks/product_added.md +++ b/docs/content/en/integrations/notification_webhooks/product_added.md @@ -12,7 +12,8 @@ X-DefectDojo-Event: product_added ## Event HTTP body ```json { - "description": null, + "description": "", + "title": "", "product": { "id": 4, "name": "notif prod", @@ -29,4 +30,4 @@ X-DefectDojo-Event: product_added "url_ui": "http://localhost:8080/product/4", "user": null } -``` \ No newline at end of file +``` diff --git a/docs/content/en/integrations/notification_webhooks/product_type_added.md b/docs/content/en/integrations/notification_webhooks/product_type_added.md index 1171f513831..e5db4139297 100644 --- a/docs/content/en/integrations/notification_webhooks/product_type_added.md +++ b/docs/content/en/integrations/notification_webhooks/product_type_added.md @@ -12,7 +12,8 @@ X-DefectDojo-Event: product_type_added ## Event HTTP body ```json { - "description": null, + "description": "", + "title": "", "product_type": { "id": 4, "name": "notif prod type", @@ -23,4 +24,4 @@ X-DefectDojo-Event: product_type_added "url_ui": "http://localhost:8080/product/type/4", "user": null } -``` \ No newline at end of file +``` diff --git a/docs/content/en/integrations/notification_webhooks/scan_added.md b/docs/content/en/integrations/notification_webhooks/scan_added.md index 27a40e6cab1..ea1a6bffa3d 100644 --- a/docs/content/en/integrations/notification_webhooks/scan_added.md +++ b/docs/content/en/integrations/notification_webhooks/scan_added.md @@ -19,7 +19,8 @@ X-DefectDojo-Event: scan_added_empty ## Event HTTP body ```json { - "description": null, + "description": "", + "title": "", "engagement": { "id": 7, "name": "notif eng", @@ -87,4 +88,4 @@ X-DefectDojo-Event: scan_added_empty "url_ui": "http://localhost:8080/test/90", "user": null } -``` \ No newline at end of file +``` diff --git a/docs/content/en/integrations/notification_webhooks/test_added.md b/docs/content/en/integrations/notification_webhooks/test_added.md index 8614a80e0a6..bf6d71dc6f5 100644 --- a/docs/content/en/integrations/notification_webhooks/test_added.md +++ b/docs/content/en/integrations/notification_webhooks/test_added.md @@ -12,7 +12,8 @@ X-DefectDojo-Event: test_added ## Event HTTP body ```json { - "description": null, + "description": "", + "title": "", "engagement": { "id": 7, "name": "notif eng", @@ -41,4 +42,4 @@ X-DefectDojo-Event: test_added "url_ui": "http://localhost:8080/test/90", "user": null } -``` \ No newline at end of file +``` diff --git a/dojo/notifications/helper.py b/dojo/notifications/helper.py index ce3f52bf1a5..46d0339dd33 100644 --- a/dojo/notifications/helper.py +++ b/dojo/notifications/helper.py @@ -153,6 +153,13 @@ def create_notification_message(event, user, notification_type, *args, **kwargs) kwargs.update({"user": user}) notification_message = None + + if (title := kwargs.get("title")) is not None: + kwargs.update({"title": title}) + + if kwargs.get("description") is None: + kwargs.update({"description": create_description(event, *args, **kwargs)}) + try: notification_message = render_to_string(template, kwargs) logger.debug("Rendering from the template %s", template) diff --git a/dojo/templates/notifications/webhooks/subtemplates/base.tpl b/dojo/templates/notifications/webhooks/subtemplates/base.tpl index 44946cd8dd3..3b6e30da989 100644 --- a/dojo/templates/notifications/webhooks/subtemplates/base.tpl +++ b/dojo/templates/notifications/webhooks/subtemplates/base.tpl @@ -1,6 +1,7 @@ {% load display_tags %} --- -description: {{ description | default_if_none:'' }} +description: "{{ description | default_if_none:'' }}" +title: "{{ title | default_if_none:'' }}" user: {{ user | default_if_none:'' }} {% if url %} url_ui: {{ url|full_url }} diff --git a/unittests/test_notifications.py b/unittests/test_notifications.py index 7f5a2b76a4c..cccdb2e3d6b 100644 --- a/unittests/test_notifications.py +++ b/unittests/test_notifications.py @@ -680,8 +680,10 @@ def test_events_messages(self, mock): with self.subTest("product_type_added"): prod_type = Product_Type.objects.create(name="notif prod type") self.assertEqual(mock.call_args.kwargs["headers"]["X-DefectDojo-Event"], "product_type_added") + self.maxDiff = None self.assertEqual(mock.call_args.kwargs["json"], { - "description": None, + "description": "Product Type notif prod type has been created successfully.", + "title": "notif prod type", "user": None, "url_api": f"http://localhost:8080/api/v2/product_types/{prod_type.pk}/", "url_ui": f"http://localhost:8080/product/type/{prod_type.pk}", @@ -696,8 +698,10 @@ def test_events_messages(self, mock): with self.subTest("product_added"): prod = Product.objects.create(name="notif prod", prod_type=prod_type) self.assertEqual(mock.call_args.kwargs["headers"]["X-DefectDojo-Event"], "product_added") + self.maxDiff = None self.assertEqual(mock.call_args.kwargs["json"], { - "description": None, + "description": "Product notif prod has been created successfully.", + "title": "notif prod", "user": None, "url_api": f"http://localhost:8080/api/v2/products/{prod.pk}/", "url_ui": f"http://localhost:8080/product/{prod.pk}", @@ -718,8 +722,10 @@ def test_events_messages(self, mock): with self.subTest("engagement_added"): eng = Engagement.objects.create(name="notif eng", product=prod, target_start=timezone.now(), target_end=timezone.now()) self.assertEqual(mock.call_args.kwargs["headers"]["X-DefectDojo-Event"], "engagement_added") + self.maxDiff = None self.assertEqual(mock.call_args.kwargs["json"], { - "description": None, + "description": "Event engagement_added has occurred.", + "title": "Engagement created for "notif prod": notif eng", "user": None, "url_api": f"http://localhost:8080/api/v2/engagements/{eng.pk}/", "url_ui": f"http://localhost:8080/engagement/{eng.pk}", @@ -747,8 +753,10 @@ def test_events_messages(self, mock): test = Test.objects.create(title="notif test", engagement=eng, target_start=timezone.now(), target_end=timezone.now(), test_type_id=Test_Type.objects.first().id) notifications_helper.notify_test_created(test) self.assertEqual(mock.call_args.kwargs["headers"]["X-DefectDojo-Event"], "test_added") + self.maxDiff = None self.assertEqual(mock.call_args.kwargs["json"], { - "description": None, + "description": "Event test_added has occurred.", + "title": "Test created for notif prod: notif eng: notif test (Acunetix Scan)", "user": None, "url_api": f"http://localhost:8080/api/v2/tests/{test.pk}/", "url_ui": f"http://localhost:8080/test/{test.pk}", @@ -781,8 +789,10 @@ def test_events_messages(self, mock): with self.subTest("scan_added_empty"): notifications_helper.notify_scan_added(test, updated_count=0) self.assertEqual(mock.call_args.kwargs["headers"]["X-DefectDojo-Event"], "scan_added_empty") + self.maxDiff = None self.assertEqual(mock.call_args.kwargs["json"], { - "description": None, + "description": "Event scan_added_empty has occurred.", + "title": "Created/Updated 0 findings for notif prod: notif eng: notif test (Acunetix Scan)", "user": None, "url_api": f"http://localhost:8080/api/v2/tests/{test.pk}/", "url_ui": f"http://localhost:8080/test/{test.pk}", From 6d811e0dc7e92428863c76a352e68eb6c631df27 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:43:40 +0100 Subject: [PATCH 62/74] File Uploads: Allow FPR format (#11157) * fix for issue #11153 * update sha sum --- dojo/settings/.settings.dist.py.sha256sum | 2 +- dojo/settings/settings.dist.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index 79cb4cfb2ea..010373c9855 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -d738d221fdbc0b2e04d9ff13d79c6cba52c49adfb81243a278252592a412d246 +fcc15cb97df6ff9ef6752a1fe498502126174285e37d67640a6cc7a83314d951 diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 9a8f28b2ddc..cfeacb9a411 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -276,7 +276,7 @@ DD_DELETE_PREVIEW=(bool, True), # List of acceptable file types that can be uploaded to a given object via arbitrary file upload DD_FILE_UPLOAD_TYPES=(list, [".txt", ".pdf", ".json", ".xml", ".csv", ".yml", ".png", ".jpeg", - ".sarif", ".xlsx", ".doc", ".html", ".js", ".nessus", ".zip"]), + ".sarif", ".xlsx", ".doc", ".html", ".js", ".nessus", ".zip", ".fpr"]), # Max file size for scan added via API in MB DD_SCAN_FILE_MAX_SIZE=(int, 100), # When disabled, existing user tokens will not be removed but it will not be From 2bbed9fb8996205ef9d4453e3c0228971a28f4a2 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:43:57 +0100 Subject: [PATCH 63/74] :tada: add tenable plugin to reference (#11151) --- dojo/tools/tenable/csv_format.py | 3 +++ unittests/tools/test_tenable_parser.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/dojo/tools/tenable/csv_format.py b/dojo/tools/tenable/csv_format.py index 2c2e0134462..3b38bd5a7af 100644 --- a/dojo/tools/tenable/csv_format.py +++ b/dojo/tools/tenable/csv_format.py @@ -103,6 +103,9 @@ def get_findings(self, filename: str, test: Test): mitigation = str(row.get("Solution", row.get("definition.solution", row.get("Steps to Remediate", "N/A")))) impact = row.get("Description", row.get("definition.description", "N/A")) references = row.get("See Also", row.get("definition.see_also", "N/A")) + references += "\nTenable Plugin ID: " + row.get("Plugin", "N/A") + references += "\nPlugin Publication Date: " + row.get("Plugin Publication Date", "N/A") + references += "\nPlugin Modification Date: " + row.get("Plugin Modification Date", "N/A") # Determine if the current row has already been processed dupe_key = ( severity diff --git a/unittests/tools/test_tenable_parser.py b/unittests/tools/test_tenable_parser.py index 7be782c49e3..681a315dd6f 100644 --- a/unittests/tools/test_tenable_parser.py +++ b/unittests/tools/test_tenable_parser.py @@ -309,3 +309,17 @@ def test_parse_issue_11102(self): endpoint.clean() self.assertEqual(2, len(findings)) self.assertEqual("Reconfigure the affected application if possible to avoid use of medium strength ciphers.", findings[0].mitigation) + + def test_parse_issue_11127(self): + with open("unittests/scans/tenable/issue_11102.csv", encoding="utf-8") as testfile: + parser = TenableParser() + findings = parser.get_findings(testfile, self.create_test()) + for finding in findings: + for endpoint in finding.unsaved_endpoints: + endpoint.clean() + reference = """https://www.openssl.org/blog/blog/2016/08/24/sweet32/ +https://sweet32.info +Tenable Plugin ID: 42873 +Plugin Publication Date: Nov 23, 2009 12:00:00 UTC +Plugin Modification Date: Feb 3, 2021 12:00:00 UTC""" + self.assertEqual(reference, findings[0].references) From 4b5c992db130e5bb906ffac104cd20a60be1bdab Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:44:43 +0100 Subject: [PATCH 64/74] :tada: fix TrivyOperator new report structure (#11156) * :tada: fix TrivyOperator new report structure * added additional info to description --- dojo/tools/trivy_operator/checks_handler.py | 14 +- dojo/tools/trivy_operator/parser.py | 23 +- dojo/tools/trivy_operator/secrets_handler.py | 15 +- .../trivy_operator/vulnerability_handler.py | 17 +- .../trivy_operator/findings_in_list.json | 399 ++++++++++++++++++ unittests/tools/test_trivy_operator_parser.py | 8 +- 6 files changed, 456 insertions(+), 20 deletions(-) create mode 100644 unittests/scans/trivy_operator/findings_in_list.json diff --git a/dojo/tools/trivy_operator/checks_handler.py b/dojo/tools/trivy_operator/checks_handler.py index e6a1ccd8bb6..c42eef0fa8a 100644 --- a/dojo/tools/trivy_operator/checks_handler.py +++ b/dojo/tools/trivy_operator/checks_handler.py @@ -10,8 +10,15 @@ class TrivyChecksHandler: - def handle_checks(self, service, checks, test): + def handle_checks(self, labels, checks, test): findings = [] + resource_namespace = labels.get("trivy-operator.resource.namespace", "") + resource_kind = labels.get("trivy-operator.resource.kind", "") + resource_name = labels.get("trivy-operator.resource.name", "") + container_name = labels.get("trivy-operator.container.name", "") + service = f"{resource_namespace}/{resource_kind}/{resource_name}" + if container_name != "": + service = f"{service}/{container_name}" for check in checks: check_title = check.get("title") check_severity = TRIVY_SEVERITIES[check.get("severity")] @@ -23,6 +30,10 @@ def handle_checks(self, service, checks, test): + check_id.lower() ) check_description = check.get("description", "") + check_description += "\n**container.name:** " + container_name + check_description += "\n**resource.kind:** " + resource_kind + check_description += "\n**resource.name:** " + resource_name + check_description += "\n**resource.namespace:** " + resource_namespace title = f"{check_id} - {check_title}" finding = Finding( test=test, @@ -33,6 +44,7 @@ def handle_checks(self, service, checks, test): static_finding=True, dynamic_finding=False, service=service, + tags=[resource_namespace], ) if check_id: finding.unsaved_vulnerability_ids = [check_id] diff --git a/dojo/tools/trivy_operator/parser.py b/dojo/tools/trivy_operator/parser.py index 138cee17624..8be42e8e31e 100644 --- a/dojo/tools/trivy_operator/parser.py +++ b/dojo/tools/trivy_operator/parser.py @@ -25,7 +25,15 @@ def get_findings(self, scan_file, test): data = json.loads(str(scan_data, "utf-8")) except Exception: data = json.loads(scan_data) + findings = [] + if type(data) is list: + for listitems in data: + findings += self.output_findings(listitems, test) + else: + findings += self.output_findings(data, test) + return findings + def output_findings(self, data, test): if data is None: return [] metadata = data.get("metadata", None) @@ -40,24 +48,15 @@ def get_findings(self, scan_file, test): benchmarkreport = benchmark.get("detailReport", None) findings = [] if report is not None: - resource_namespace = labels.get( - "trivy-operator.resource.namespace", "", - ) - resource_kind = labels.get("trivy-operator.resource.kind", "") - resource_name = labels.get("trivy-operator.resource.name", "") - container_name = labels.get("trivy-operator.container.name", "") - service = f"{resource_namespace}/{resource_kind}/{resource_name}" - if container_name != "": - service = f"{service}/{container_name}" vulnerabilities = report.get("vulnerabilities", None) if vulnerabilities is not None: - findings += TrivyVulnerabilityHandler().handle_vulns(service, vulnerabilities, test) + findings += TrivyVulnerabilityHandler().handle_vulns(labels, vulnerabilities, test) checks = report.get("checks", None) if checks is not None: - findings += TrivyChecksHandler().handle_checks(service, checks, test) + findings += TrivyChecksHandler().handle_checks(labels, checks, test) secrets = report.get("secrets", None) if secrets is not None: - findings += TrivySecretsHandler().handle_secrets(service, secrets, test) + findings += TrivySecretsHandler().handle_secrets(labels, secrets, test) elif benchmarkreport is not None: findings += TrivyComplianceHandler().handle_compliance(benchmarkreport, test) return findings diff --git a/dojo/tools/trivy_operator/secrets_handler.py b/dojo/tools/trivy_operator/secrets_handler.py index c5e767a1bc5..a00c894a034 100644 --- a/dojo/tools/trivy_operator/secrets_handler.py +++ b/dojo/tools/trivy_operator/secrets_handler.py @@ -15,8 +15,15 @@ class TrivySecretsHandler: - def handle_secrets(self, service, secrets, test): + def handle_secrets(self, labels, secrets, test): findings = [] + resource_namespace = labels.get("trivy-operator.resource.namespace", "") + resource_kind = labels.get("trivy-operator.resource.kind", "") + resource_name = labels.get("trivy-operator.resource.name", "") + container_name = labels.get("trivy-operator.container.name", "") + service = f"{resource_namespace}/{resource_kind}/{resource_name}" + if container_name != "": + service = f"{service}/{container_name}" for secret in secrets: secret_title = secret.get("title") secret_category = secret.get("category") @@ -31,7 +38,10 @@ def handle_secrets(self, service, secrets, test): category=secret_category, match=secret_match, ) - + secret_description += "\n**container.name:** " + container_name + secret_description += "\n**resource.kind:** " + resource_kind + secret_description += "\n**resource.name:** " + resource_name + secret_description += "\n**resource.namespace:** " + resource_namespace finding = Finding( test=test, title=title, @@ -42,6 +52,7 @@ def handle_secrets(self, service, secrets, test): static_finding=True, dynamic_finding=False, service=service, + tags=[resource_namespace], ) if secret_rule_id: finding.unsaved_vulnerability_ids = [secret_rule_id] diff --git a/dojo/tools/trivy_operator/vulnerability_handler.py b/dojo/tools/trivy_operator/vulnerability_handler.py index 13be3e55a41..a5a26e1288a 100644 --- a/dojo/tools/trivy_operator/vulnerability_handler.py +++ b/dojo/tools/trivy_operator/vulnerability_handler.py @@ -14,8 +14,15 @@ class TrivyVulnerabilityHandler: - def handle_vulns(self, service, vulnerabilities, test): + def handle_vulns(self, labels, vulnerabilities, test): findings = [] + resource_namespace = labels.get("trivy-operator.resource.namespace", "") + resource_kind = labels.get("trivy-operator.resource.kind", "") + resource_name = labels.get("trivy-operator.resource.name", "") + container_name = labels.get("trivy-operator.container.name", "") + service = f"{resource_namespace}/{resource_kind}/{resource_name}" + if container_name != "": + service = f"{service}/{container_name}" for vulnerability in vulnerabilities: vuln_id = vulnerability.get("vulnerabilityID", "0") severity = TRIVY_SEVERITIES[vulnerability.get("severity")] @@ -24,8 +31,7 @@ def handle_vulns(self, service, vulnerabilities, test): package_name = vulnerability.get("resource") package_version = vulnerability.get("installedVersion") cvssv3_score = vulnerability.get("score") - - finding_tags = [] + finding_tags = [resource_namespace] target_target = None target_class = None package_path = None @@ -57,7 +63,10 @@ def handle_vulns(self, service, vulnerabilities, test): description = DESCRIPTION_TEMPLATE.format( title=vulnerability.get("title"), fixed_version=mitigation, ) - + description += "\n**container.name:** " + container_name + description += "\n**resource.kind:** " + resource_kind + description += "\n**resource.name:** " + resource_name + description += "\n**resource.namespace:** " + resource_namespace title = f"{vuln_id} {package_name} {package_version}" finding = Finding( test=test, diff --git a/unittests/scans/trivy_operator/findings_in_list.json b/unittests/scans/trivy_operator/findings_in_list.json new file mode 100644 index 00000000000..7a79600eb8f --- /dev/null +++ b/unittests/scans/trivy_operator/findings_in_list.json @@ -0,0 +1,399 @@ +[ + { + "apiVersion": "aquasecurity.github.io/v1alpha1", + "kind": "ConfigAuditReport", + "metadata": { + "annotations": { + "trivy-operator.aquasecurity.github.io/report-ttl": "24h0m0s" + }, + "creationTimestamp": "2023-03-23T16:22:54Z", + "generation": 1, + "labels": { + "plugin-config-hash": "659b7b9c46", + "resource-spec-hash": "fc85b485f", + "trivy-operator.resource.kind": "ReplicaSet", + "trivy-operator.resource.name": "nginx-deployment-965685897", + "trivy-operator.resource.namespace": "default" + }, + "name": "replicaset-nginx-deployment-965685897", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": false, + "controller": true, + "kind": "ReplicaSet", + "name": "nginx-deployment-965685897", + "uid": "d19c7f74-b4c3-429d-9a45-1b2f5efc3c88" + } + ], + "resourceVersion": "1268", + "uid": "a92e0951-e988-419d-8602-6852f920ce06" + }, + "report": { + "checks": [ + { + "category": "Kubernetes Security Check", + "checkID": "KSV014", + "description": "An immutable root file system prevents applications from writing to their local disk. This can limit intrusions, as attackers will not be able to tamper with the file system or write foreign executables to disk.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'securityContext.readOnlyRootFilesystem' to true" + ], + "severity": "LOW", + "success": false, + "title": "Root file system is not read-only" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV016", + "description": "When containers have memory requests specified, the scheduler can make better decisions about which nodes to place pods on, and how to deal with resource contention.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'resources.requests.memory'" + ], + "severity": "LOW", + "success": false, + "title": "Memory requests not specified" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV030", + "description": "The RuntimeDefault/Localhost seccomp profile must be required, or allow specific additional profiles.", + "messages": [ + "Either Pod or Container should set 'securityContext.seccompProfile.type' to 'RuntimeDefault'" + ], + "severity": "LOW", + "success": false, + "title": "Default Seccomp profile not set" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV015", + "description": "When containers have resource requests specified, the scheduler can make better decisions about which nodes to place pods on, and how to deal with resource contention.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'resources.requests.cpu'" + ], + "severity": "LOW", + "success": false, + "title": "CPU requests not specified" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV011", + "description": "Enforcing CPU limits prevents DoS via resource exhaustion.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'resources.limits.cpu'" + ], + "severity": "LOW", + "success": false, + "title": "CPU not limited" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV012", + "description": "'runAsNonRoot' forces the running image to run as a non-root user to ensure least privileges.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'securityContext.runAsNonRoot' to true" + ], + "severity": "MEDIUM", + "success": false, + "title": "Runs as root user" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV018", + "description": "Enforcing memory limits prevents DoS via resource exhaustion.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'resources.limits.memory'" + ], + "severity": "LOW", + "success": false, + "title": "Memory not limited" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV110", + "description": "ensure that default namespace should not be used", + "messages": [ + "ReplicaSet 'nginx-deployment-965685897' should not be set with 'default' namespace" + ], + "severity": "LOW", + "success": false, + "title": "The default namespace should not be used" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV003", + "description": "The container should drop all default capabilities and add only those that are needed for its execution.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should add 'ALL' to 'securityContext.capabilities.drop'" + ], + "severity": "LOW", + "success": false, + "title": "Default capabilities not dropped" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV021", + "description": "Force the container to run with group ID \u003e 10000 to avoid conflicts with the host’s user table.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'securityContext.runAsGroup' \u003e 10000" + ], + "severity": "LOW", + "success": false, + "title": "Runs with low group ID" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV001", + "description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'securityContext.allowPrivilegeEscalation' to false" + ], + "severity": "MEDIUM", + "success": false, + "title": "Process can elevate its own privileges" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV106", + "description": "Containers must drop ALL capabilities, and are only permitted to add back the NET_BIND_SERVICE capability.", + "messages": [ + "container should drop all" + ], + "severity": "LOW", + "success": false, + "title": "Container capabilities must only include NET_BIND_SERVICE" + }, + { + "category": "Kubernetes Security Check", + "checkID": "KSV020", + "description": "Force the container to run with user ID \u003e 10000 to avoid conflicts with the host’s user table.", + "messages": [ + "Container 'nginx' of ReplicaSet 'nginx-deployment-965685897' should set 'securityContext.runAsUser' \u003e 10000" + ], + "severity": "LOW", + "success": false, + "title": "Runs with low user ID" + } + ], + "scanner": { + "name": "Trivy", + "vendor": "Aqua Security", + "version": "dev" + }, + "summary": { + "criticalCount": 0, + "highCount": 0, + "lowCount": 11, + "mediumCount": 2 + }, + "updateTimestamp": "2023-03-23T16:22:54Z" + } + }, + { + "kind": "VulnerabilityReport", + "apiVersion": "aquasecurity.github.io/v1alpha1", + "metadata": { + "name": "pod-ubuntu-ubuntu", + "namespace": "lbc", + "uid": "e2c1fa59-051b-479d-ab47-f7bf6e7f858d", + "resourceVersion": "26700784781", + "generation": 1, + "creationTimestamp": "2024-01-23T13:43:55Z", + "labels": { + "resource-spec-hash": "666674544b", + "trivy-operator.container.name": "ubuntu", + "trivy-operator.resource.kind": "Pod", + "trivy-operator.resource.name": "ubuntu", + "trivy-operator.resource.namespace": "lbc" + }, + "annotations": { + "trivy-operator.aquasecurity.github.io/report-ttl": "24h0m0s" + }, + "ownerReferences": [ + { + "apiVersion": "v1", + "kind": "Pod", + "name": "ubuntu", + "uid": "aa8d6ec8-5417-4190-93e9-6d4d78dc8da9", + "controller": true, + "blockOwnerDeletion": false + } + ], + "managedFields": [ + { + "manager": "trivy-operator", + "operation": "Update", + "apiVersion": "aquasecurity.github.io/v1alpha1", + "time": "2024-01-23T13:43:55Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:trivy-operator.aquasecurity.github.io/report-ttl": {} + }, + "f:labels": { + ".": {}, + "f:resource-spec-hash": {}, + "f:trivy-operator.container.name": {}, + "f:trivy-operator.resource.kind": {}, + "f:trivy-operator.resource.name": {}, + "f:trivy-operator.resource.namespace": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"aa8d6ec8-5417-4190-93e9-6d4d78dc8da9\"}": {} + } + }, + "f:report": { + ".": {}, + "f:artifact": { + ".": {}, + "f:digest": {}, + "f:repository": {}, + "f:tag": {} + }, + "f:os": { + ".": {}, + "f:family": {}, + "f:name": {} + }, + "f:registry": { + ".": {}, + "f:server": {} + }, + "f:scanner": { + ".": {}, + "f:name": {}, + "f:vendor": {}, + "f:version": {} + }, + "f:summary": { + ".": {}, + "f:criticalCount": {}, + "f:highCount": {}, + "f:lowCount": {}, + "f:mediumCount": {}, + "f:noneCount": {}, + "f:unknownCount": {} + }, + "f:updateTimestamp": {}, + "f:vulnerabilities": {} + } + } + } + ] + }, + "report": { + "updateTimestamp": "2024-01-23T13:43:55Z", + "scanner": { + "name": "Trivy", + "vendor": "Aqua Security", + "version": "0.48.3" + }, + "registry": { + "server": "index.docker.io" + }, + "artifact": { + "repository": "library/ubuntu", + "digest": "sha256:f78909c2b360d866b3220655c0b079838258b8891a12ac25fc670f0cbb54229f", + "tag": "20.04" + }, + "os": { + "family": "ubuntu", + "name": "20.04" + }, + "summary": { + "criticalCount": 0, + "highCount": 0, + "mediumCount": 5, + "lowCount": 0, + "unknownCount": 0, + "noneCount": 0 + }, + "vulnerabilities": [ + { + "vulnerabilityID": "CVE-2024-0553", + "resource": "libgnutls30", + "installedVersion": "3.6.13-2ubuntu1.9", + "fixedVersion": "3.6.13-2ubuntu1.10", + "publishedDate": "2024-01-16T12:15:45Z", + "lastModifiedDate": "2024-01-19T21:15:08Z", + "severity": "MEDIUM", + "title": "gnutls: incomplete fix for CVE-2023-5981", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-0553", + "links": [], + "score": 5.9, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + }, + { + "vulnerabilityID": "CVE-2024-22365", + "resource": "libpam-modules", + "installedVersion": "1.3.1-5ubuntu4.6", + "fixedVersion": "1.3.1-5ubuntu4.7", + "publishedDate": "", + "lastModifiedDate": "", + "severity": "MEDIUM", + "title": "pam: allowing unpriledged user to block another user namespace", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-22365", + "links": [], + "score": 5.5, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + }, + { + "vulnerabilityID": "CVE-2024-22365", + "resource": "libpam-modules-bin", + "installedVersion": "1.3.1-5ubuntu4.6", + "fixedVersion": "1.3.1-5ubuntu4.7", + "publishedDate": "", + "lastModifiedDate": "", + "severity": "MEDIUM", + "title": "pam: allowing unpriledged user to block another user namespace", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-22365", + "links": [], + "score": 5.5, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + }, + { + "vulnerabilityID": "CVE-2024-22365", + "resource": "libpam-runtime", + "installedVersion": "1.3.1-5ubuntu4.6", + "fixedVersion": "1.3.1-5ubuntu4.7", + "publishedDate": "", + "lastModifiedDate": "", + "severity": "MEDIUM", + "title": "pam: allowing unpriledged user to block another user namespace", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-22365", + "links": [], + "score": 5.5, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + }, + { + "vulnerabilityID": "CVE-2024-22365", + "resource": "libpam0g", + "installedVersion": "1.3.1-5ubuntu4.6", + "fixedVersion": "1.3.1-5ubuntu4.7", + "publishedDate": "", + "lastModifiedDate": "", + "severity": "MEDIUM", + "title": "pam: allowing unpriledged user to block another user namespace", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-22365", + "links": [], + "score": 5.5, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + } + ] + } + } +] \ No newline at end of file diff --git a/unittests/tools/test_trivy_operator_parser.py b/unittests/tools/test_trivy_operator_parser.py index 8ac6b5f3189..5e4a71558da 100644 --- a/unittests/tools/test_trivy_operator_parser.py +++ b/unittests/tools/test_trivy_operator_parser.py @@ -135,7 +135,7 @@ def test_vulnerabilityreport_extended(self): self.assertEqual("3.6.13-2ubuntu1.10", finding.mitigation) self.assertEqual(5.9, finding.cvssv3_score) self.assertEqual("ubuntu:20.04 (ubuntu 20.04)", finding.file_path) - self.assertEqual("os-pkgs, ubuntu", str(finding.tags)) + self.assertEqual("lbc, os-pkgs, ubuntu", str(finding.tags)) def test_cis_benchmark(self): with open(sample_path("cis_benchmark.json"), encoding="utf-8") as test_file: @@ -157,3 +157,9 @@ def test_cis_benchmark(self): self.assertEqual("Medium", finding.severity) self.assertEqual(1, len(finding.unsaved_vulnerability_ids)) self.assertEqual("AVD-KSV-0012", finding.unsaved_vulnerability_ids[0]) + + def test_findings_in_list(self): + with open(sample_path("findings_in_list.json"), encoding="utf-8") as test_file: + parser = TrivyOperatorParser() + findings = parser.get_findings(test_file, Test()) + self.assertEqual(len(findings), 18) From 68e0a8bae0b173c98ff6fd924ba5ea36071c1067 Mon Sep 17 00:00:00 2001 From: Harold Blankenship <36673698+hblankenship@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:45:10 -0500 Subject: [PATCH 65/74] use engagement_end_date (#11174) --- dojo/api_v2/serializers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index 56a18265064..bc9cbc03b86 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -2258,6 +2258,13 @@ def setup_common_context(self, data: dict) -> dict: if context.get("scan_date") else None ) + + # engagement end date was not being used at all and so target_end would also turn into None + # in this case, do not want to change target_end unless engagement_end exists + eng_end_date = context.get("engagement_end_date", None) + if eng_end_date: + context["target_end"] = context.get("engagement_end_date") + return context From 8a08e60fabb4c93c546f668fae73a921f88d4daf Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Sat, 2 Nov 2024 02:59:22 +0100 Subject: [PATCH 66/74] fix unittest documentation #11128 (#11143) * fix unittest documentation #11128 * udpate * Update docs/content/en/contributing/how-to-write-a-parser.md Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update docs/content/en/contributing/how-to-write-a-parser.md Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> --------- Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> --- .../en/contributing/how-to-write-a-parser.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/content/en/contributing/how-to-write-a-parser.md b/docs/content/en/contributing/how-to-write-a-parser.md index c87846cb620..5652f0dbc59 100644 --- a/docs/content/en/contributing/how-to-write-a-parser.md +++ b/docs/content/en/contributing/how-to-write-a-parser.md @@ -16,9 +16,9 @@ All commands assume that you're located at the root of the django-DefectDojo clo - It's advised that you create a dedicated branch for your development, such as `git checkout -b parser-name`. It is easiest to use the docker compose deployment as it has hot-reload capbility for uWSGI. -Set up your environment to use the debug environment: +Set up your environment to use the dev environment: -`$ docker/setEnv.sh debug` +`$ docker/setEnv.sh dev` Please have a look at [DOCKER.md](https://github.com/DefectDojo/django-DefectDojo/blob/master/readme-docs/DOCKER.md) for more details. @@ -294,12 +294,24 @@ This local command will launch the unit test for your new parser $ docker compose exec uwsgi bash -c 'python manage.py test unittests.tools.. -v2' {{< /highlight >}} +or like this: + +{{< highlight bash >}} +$ ./dc-unittest.sh --test-case unittests.tools.. +{{< /highlight >}} + Example for the blackduck hub parser: {{< highlight bash >}} $ docker compose exec uwsgi bash -c 'python manage.py test unittests.tools.test_blackduck_csv_parser.TestBlackduckHubParser -v2' {{< /highlight >}} +or like this: + +{{< highlight bash >}} +$ ./dc-unittest.sh --test-case unittests.tools.test_blackduck_csv_parser.TestBlackduckHubParser +{{< /highlight >}} + {{% alert title="Information" color="info" %}} If you want to run all unit tests, simply run `$ docker compose exec uwsgi bash -c 'python manage.py test unittests -v2'` {{% /alert %}} From 8b1242aa3a2daea316f3cfb014b92c7a5a41748d Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Sat, 2 Nov 2024 03:20:44 +0100 Subject: [PATCH 67/74] Ruff: Add and fix S112, S311 (#11098) --- ruff.toml | 2 +- unittests/test_tags.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ruff.toml b/ruff.toml index 0f3ede2f2c2..cb7e9231257 100644 --- a/ruff.toml +++ b/ruff.toml @@ -41,7 +41,7 @@ select = [ "UP", "YTT", "ASYNC", - "S101", "S2", "S5", "S7", + "S2", "S5", "S7", "S101", "S112", "S311", "FBT001", "FBT003", "A003", "A004", "A006", "COM", diff --git a/unittests/test_tags.py b/unittests/test_tags.py index a1e1aa20cc1..3f93129fa82 100644 --- a/unittests/test_tags.py +++ b/unittests/test_tags.py @@ -24,7 +24,7 @@ def create_finding_with_tags(self, tags): del finding_details["id"] - finding_details["title"] = "tags test " + str(random.randint(1, 9999)) + finding_details["title"] = "tags test " + str(random.randint(1, 9999)) # noqa: S311 finding_details["tags"] = tags response = self.post_new_finding_api(finding_details) From 649528ebaa964d61039f545d0a3eb91ebd894fc7 Mon Sep 17 00:00:00 2001 From: siniysv Date: Mon, 4 Nov 2024 17:01:18 +0100 Subject: [PATCH 68/74] Parser for AWS Inspector2 findings (#10829) * Create parser for AWS Inspector2 findings * Parser cleanup * Fix ruff * Fix ruff * Update hash code fields --------- Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> --- .../parsers/file/aws_inspector2.md | 24 + dojo/settings/.settings.dist.py.sha256sum | 2 +- dojo/settings/settings.dist.py | 3 + dojo/tools/aws_inspector2/__init__.py | 0 dojo/tools/aws_inspector2/parser.py | 255 +++++++ .../aws_inspector2_many_vul.json | 680 ++++++++++++++++++ .../aws_inspector2_one_vul.json | 89 +++ .../aws_inspector2_zero_vul.json | 3 + .../aws_inspector2/empty_with_error.json | 1 + unittests/tools/test_aws_inspector2_parser.py | 46 ++ 10 files changed, 1102 insertions(+), 1 deletion(-) create mode 100644 docs/content/en/integrations/parsers/file/aws_inspector2.md create mode 100644 dojo/tools/aws_inspector2/__init__.py create mode 100644 dojo/tools/aws_inspector2/parser.py create mode 100644 unittests/scans/aws_inspector2/aws_inspector2_many_vul.json create mode 100644 unittests/scans/aws_inspector2/aws_inspector2_one_vul.json create mode 100644 unittests/scans/aws_inspector2/aws_inspector2_zero_vul.json create mode 100644 unittests/scans/aws_inspector2/empty_with_error.json create mode 100644 unittests/tools/test_aws_inspector2_parser.py diff --git a/docs/content/en/integrations/parsers/file/aws_inspector2.md b/docs/content/en/integrations/parsers/file/aws_inspector2.md new file mode 100644 index 00000000000..d7507b61688 --- /dev/null +++ b/docs/content/en/integrations/parsers/file/aws_inspector2.md @@ -0,0 +1,24 @@ +--- +title: "AWS Inspector2 Scanner" +toc_hide: true +--- + +### File Types +AWS Inspector2 report can be imported in json format. Inspector2 name comes from API calls to "modern" Inspector API - `aws inspector2` as opposite to Classic Inspector (previous version of the service), this is an example of how such report can be generated: `aws inspector2 list-findings --filter-criteria '{"resourceId":[{"comparison":"EQUALS","value":"i-instance_id_here"}]}' --region us-east-1 > inspector2_findings.json` + + +This parser can help to get findings in a delegated admin account for AWS Inspector or in a standalone AWS account. The parser is developed mostly for a scenario where findings are obtained for a specific resource like an ECR image or an instance, and uploaded to a test in a DefectDojo engagement that represents a branch from a git repository. + + +A minimal valid json file with no findings: + +```json +{ + "findings": [] +} +``` + +Detailed API response format can be obtained [here](https://docs.aws.amazon.com/inspector/v2/APIReference/API_Finding.html) + +### Sample Scan Data +Sample AWS Inspector2 findings can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/aws_inspector2). diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index 2a9d5c52e55..9206f7bf5ed 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -6a90a111e2b89eb2c400945c80ff76c64b135d78b84fdf6b09a6b83569946904 +4b0c6ee05222e622f74d80c8e93504aba986ad0b187aab305ff7ecef89080f11 diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index d84a64b047f..91f72e40c19 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1283,6 +1283,7 @@ def saml2_attrib_map_format(dict): "Kiuwan SCA Scan": ["description", "severity", "component_name", "component_version", "cwe"], "Rapplex Scan": ["title", "endpoints", "severity"], "AppCheck Web Application Scanner": ["title", "severity"], + "AWS Inspector2 Scan": ["title", "severity", "description"], "Legitify Scan": ["title", "endpoints", "severity"], "ThreatComposer Scan": ["title", "description"], "Invicti Scan": ["title", "description", "severity"], @@ -1350,6 +1351,7 @@ def saml2_attrib_map_format(dict): "Wazuh": True, "Nuclei Scan": True, "Threagile risks report": True, + "AWS Inspector2 Scan": True, } # List of fields that are known to be usable in hash_code computation) @@ -1510,6 +1512,7 @@ def saml2_attrib_map_format(dict): "Kiuwan SCA Scan": DEDUPE_ALGO_HASH_CODE, "Rapplex Scan": DEDUPE_ALGO_HASH_CODE, "AppCheck Web Application Scanner": DEDUPE_ALGO_HASH_CODE, + "AWS Inspector2 Scan": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE, "Legitify Scan": DEDUPE_ALGO_HASH_CODE, "ThreatComposer Scan": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE, "Invicti Scan": DEDUPE_ALGO_HASH_CODE, diff --git a/dojo/tools/aws_inspector2/__init__.py b/dojo/tools/aws_inspector2/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/tools/aws_inspector2/parser.py b/dojo/tools/aws_inspector2/parser.py new file mode 100644 index 00000000000..863c9ebc18a --- /dev/null +++ b/dojo/tools/aws_inspector2/parser.py @@ -0,0 +1,255 @@ +import json +from datetime import UTC, datetime + +from dateutil import parser as date_parser + +from dojo.models import Endpoint, Finding + + +class AWSInspector2Parser: + + """Import AWS Inspector2 json.""" + + def get_scan_types(self): + return ["AWS Inspector2 Scan"] + + def get_label_for_scan_types(self, scan_type): + return "AWS Inspector2 Scan" + + def get_description_for_scan_types(self, scan_type): + return "AWS Inspector2 report file can be imported in JSON format (aws inspector2 list-findings)." + + def get_findings(self, file, test): + tree = json.load(file) + raw_findings = tree.get("findings", None) + if not isinstance(raw_findings, list): + msg = "Incorrect Inspector2 report format" + raise TypeError(msg) + self.test = test + findings = [] + for raw_finding in raw_findings: + finding = self.get_base_finding(raw_finding) + # type specific details + finding_type = raw_finding.get("type", None) + if finding_type == "PACKAGE_VULNERABILITY": + finding = self.get_package_vulnerability(finding, raw_finding) + elif finding_type == "CODE_VULNERABILITY": + finding = self.get_code_vulnerability(finding, raw_finding) + elif finding_type == "NETWORK_REACHABILITY": + finding = self.get_network_reachability(finding, raw_finding) + else: + msg = "Incorrect Inspector2 report format" + raise TypeError(msg) + # process the endpoints + finding = self.process_endpoints(finding, raw_finding) + findings.append(finding) + + return findings + + def get_severity(self, severity_string): + if severity_string == "UNTRIAGED": + severity_string = "Info" + return severity_string.title() + + def get_base_finding(self, raw_finding: dict) -> Finding: + # basic fields + finding_id = raw_finding.get("findingArn") + title = raw_finding.get("title", "The title could not be identified...") + description = "" + if (aws_account := raw_finding.get("awsAccountId")) is not None: + description += f"**AWS Account**: {aws_account}\n" + if finding_id is not None: + description += f"**Finding ARN**: {finding_id}\n" + if (inspector_score := raw_finding.get("inspectorScore")) is not None: + description += f"Inspector score: {inspector_score}\n" + if (discovered_at := raw_finding.get("firstObservedAt")) is not None: + description += f"Discovered at: {discovered_at}\n" + if (last_seen_at := raw_finding.get("lastObservedAt")) is not None: + description += f"Last seen: {last_seen_at}\n" + if (orig_description := raw_finding.get("description")) is not None: + description += f"Original description: \n{orig_description}\n" + finding = Finding( + title=title, + test=self.test, + description=description, + severity=self.get_severity(raw_finding.get("severity", "Info")), + unique_id_from_tool=finding_id, + static_finding=True, + dynamic_finding=False, + ) + # set mitigation status + if raw_finding.get("status", "ACTIVE") == "ACTIVE": + mitigated = None + is_mitigated = False + active = True + else: + is_mitigated = True + active = False + if (last_observed := raw_finding.get("lastObservedAt", None)) is not None: + mitigated = date_parser(last_observed) + else: + mitigated = datetime.now(UTC) + finding.active = active + finding.is_mitigated = is_mitigated + finding.mitigated = mitigated + # EPSS + finding.epss_score = raw_finding.get("epss", {}).get("score", None) + + return finding + + def get_package_vulnerability(self, finding: Finding, raw_finding: dict) -> Finding: + vulnerability_details = raw_finding.get("packageVulnerabilityDetails", {}) + vulnerability_packages_descriptions = "\n".join( + [ + ( + f'*Vulnerable package*: {vulnerability_package.get("name", "N/A")}\n' + f'\tpackage manager: {vulnerability_package.get("packageManager", "N/A")}\n' + f'\tversion: {vulnerability_package.get("version", "N/A")}\n' + f'\tfixed version: {vulnerability_package.get("fixedInVersion", "N/A")}\n' + f'\tremediation: {vulnerability_package.get("remediation", "N/A")}\n' + ) + for vulnerability_package in vulnerability_details.get("vulnerablePackages", []) + ], + ) + if (vulnerability_id := vulnerability_details.get("vulnerabilityId", None)) is not None: + finding.unsaved_vulnerability_ids = [vulnerability_id] + vulnerability_source = vulnerability_details.get("source") + vulnerability_source_url = vulnerability_details.get("sourceUrl") + # populate fields + if vulnerability_source is not None and vulnerability_source_url is not None: + finding.url = vulnerability_source_url + finding.description += ( + "\n**Additional info**\n" + f"Vulnerability info from: {vulnerability_source} {vulnerability_source_url}\n" + "Affected packages:\n" + f"{vulnerability_packages_descriptions}\n" + ) + + return finding + + def get_code_vulnerability(self, finding: Finding, raw_finding: dict) -> Finding: + cwes = raw_finding.get("cwes", []) + detector_id = raw_finding.get("detectorId", "N/A") + detector_name = raw_finding.get("detectorName", "N/A") + file_path_info = raw_finding.get("filePath", {}) + file_name = file_path_info.get("fileName", "N/A") + file_path = file_path_info.get("filePath", "N/A") + start_line = file_path_info.get("startLine", "N/A") + end_line = file_path_info.get("endLine", "N/A") + detector_tags = ", ".join(raw_finding.get("detectorTags", [])) + reference_urls = ", ".join(raw_finding.get("referenceUrls", [])) + rule_id = raw_finding.get("ruleId", "N/A") + layer_arn = raw_finding.get("sourceLambdaLayerArn", "N/A") + string_cwes = ", ".join(cwes) + # populate fields + finding.cwe = cwes[0] if cwes else None + finding.file_path = f"{file_path}{file_name}" + finding.sast_source_file_path = f"{file_path}{file_name}" + finding.line = start_line + finding.sast_source_line = start_line + finding.description += ( + "\n**Additional info**\n" + f"CWEs: {string_cwes}\n" + f"Vulnerability info from: {detector_id} {detector_name}\n" + f"Rule: {rule_id}\n" + f"Lines: {start_line} - {end_line}\n" + f"Tags: {detector_tags or 'N/A'}\n" + f"URLs: {reference_urls or 'N/A'}\n" + f"Lambda layer ARN: {layer_arn}\n" + ) + + return finding + + def get_network_reachability(self, finding: Finding, raw_finding: dict) -> Finding: + network_path_info = raw_finding.get("networkPath", {}) + network_path_steps = network_path_info.get("steps", []) + steps_descriptions = "\n".join( + [ + f'steps:\n{step_number}: {step.get("componentId", "N/A")} {step.get("componentType", "N/A")}' + for step_number, step in enumerate(network_path_steps) + ], + ) + open_port_range_info = raw_finding.get("openPortRange", {}) + port_range_start = open_port_range_info.get("begin", "N/A") + port_range_end = open_port_range_info.get("end", "N/A") + protocol = raw_finding.get("protocol", "N/A") + finding.description += ( + "\n**Additional info**\n" + f"protocol {protocol}, port range {port_range_start} - {port_range_end}" + f"{steps_descriptions}\n" + ) + + return finding + + def process_endpoints(self, finding: Finding, raw_finding: dict) -> Finding: + impact = [] + endpoints = [] + for resource_info in raw_finding.get("resources", {}): + resource_type = resource_info.get("type", None) + resource_id = resource_info.get("id", "N/A") + resource_details = resource_info.get("details", {}) + endpoint_host = f"{resource_type} - {resource_id}" + if resource_type == "AWS_EC2_INSTANCE": + aws_account = raw_finding.get("awsAccountId") + resource_region = resource_info.get("region", "N/A") + endpoint_host = resource_id + ec2_instance_details = resource_details.get("awsEc2Instance", None) + if ec2_instance_details: + impact.extend( + ( + f"ARN: {resource_id}", + f"Image ID: {ec2_instance_details.get('imageId', 'N/A')}", + f"IPv4 address: {ec2_instance_details.get('ipV4Addresses', 'N/A')}", + f"Subnet: {ec2_instance_details.get('subnetId', 'N/A')}", + f"VPC: {ec2_instance_details.get('vpcId', 'N/A')}", + f"Region: {resource_region}", + f"AWS Account: {aws_account}", + f"Launched at: {ec2_instance_details.get('launchedAt', 'N/A')}", + "---", + ), + ) + elif resource_type == "AWS_ECR_CONTAINER_IMAGE": + image_id = resource_id.split("repository/")[1].replace("sha256:", "").replace("/", "-") + endpoint_host = image_id + ecr_image_details = resource_details.get("awsEcrContainerImage", None) + if ecr_image_details: + impact.extend( + ( + f"ARN: {resource_id}", + f"Registry: {ecr_image_details.get('registry', 'N/A')}", + f"Repository: {ecr_image_details.get('repositoryName', 'N/A')}", + f"Hash: {ecr_image_details.get('imageHash', 'N/A')}", + f"Author: {ecr_image_details.get('author', 'N/A')}", + f"Pushed at: {ecr_image_details.get('pushedAt', 'N/A')}", + "---", + ), + ) + elif resource_type == "AWS_ECR_REPOSITORY": + # no corresponding + # key present in + # https://docs.aws.amazon.com/inspector/v2/APIReference/API_ResourceDetails.html + pass + elif resource_type == "AWS_LAMBDA_FUNCTION": + lambda_id = resource_id.split("function:")[1].replace(":", "-").replace("/", "-") + endpoint_host = lambda_id + lambda_details = resource_details.get("awsLambdaFunction", None) + if lambda_details: + impact.extend( + ( + f"ARN: {resource_id}", + f"Name: {lambda_details.get('functionName', 'N/A')}", + f"Version: {lambda_details.get('version', 'N/A')}", + f"Runtime: {lambda_details.get('runtime', 'N/A')}", + f"Hash: {lambda_details.get('codeSha256', 'N/A')}", + f"Pushed at: {lambda_details.get('lastModifiedAt', 'N/A')}", + ), + ) + else: + msg = "Incorrect Inspector2 report format" + raise TypeError(msg) + endpoints.append(Endpoint(host=endpoint_host)) + finding.impact = "\n".join(impact) + finding.unsaved_endpoints = [] + finding.unsaved_endpoints.extend(endpoints) + + return finding diff --git a/unittests/scans/aws_inspector2/aws_inspector2_many_vul.json b/unittests/scans/aws_inspector2/aws_inspector2_many_vul.json new file mode 100644 index 00000000000..c3083037c69 --- /dev/null +++ b/unittests/scans/aws_inspector2/aws_inspector2_many_vul.json @@ -0,0 +1,680 @@ +{ + "findings": [ + { + "awsAccountId": "012345678901", + "description": "A memory leak flaw was found in the Linux kernel in the ccp_run_aes_gcm_cmd() function in drivers/crypto/ccp/ccp-ops.c, which allows attackers to cause a denial of service (memory consumption). This vulnerability is similar with the older CVE-2019-18808.", + "epss": { + "score": 0.00075 + }, + "exploitAvailable": "NO", + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/00037f625ee77ac186869f906eb73366", + "firstObservedAt": "2024-02-02T16:30:33.045000+01:00", + "fixAvailable": "YES", + "inspectorScore": 5.5, + "inspectorScoreDetails": { + "adjustedCvss": { + "adjustments": [], + "cvssSource": "NVD", + "score": 5.5, + "scoreSource": "NVD", + "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + "version": "3.1" + } + }, + "lastObservedAt": "2024-06-14T04:03:53.051000+02:00", + "packageVulnerabilityDetails": { + "cvss": [ + { + "baseScore": 5.5, + "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + "source": "NVD", + "version": "3.1" + } + ], + "referenceUrls": [], + "relatedVulnerabilities": [], + "source": "DEBIAN_CVE", + "sourceUrl": "https://security-tracker.debian.org/tracker/CVE-2021-3744", + "vendorSeverity": "unimportant", + "vulnerabilityId": "CVE-2021-3744", + "vulnerablePackages": [ + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "0:4.19.232-1", + "name": "linux", + "packageManager": "OS", + "release": "2", + "remediation": "apt-get update && apt-get upgrade", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "4.19.118" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEcrContainerImage": { + "architecture": "amd64", + "author": "John D ", + "imageHash": "sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "imageTags": [ + "2.3.0" + ], + "platform": "DEBIAN_10", + "pushedAt": "2020-06-10T11:30:35+02:00", + "registry": "012345678901", + "repositoryName": "my-awesome-docker-image" + } + }, + "id": "arn:aws:ecr:us-east-1:012345678901:repository/my-awesome-docker-image/sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "partition": "aws", + "region": "us-east-1", + "tags": {}, + "type": "AWS_ECR_CONTAINER_IMAGE" + } + ], + "severity": "MEDIUM", + "status": "ACTIVE", + "title": "CVE-2021-3744 - linux", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-06-14T04:03:53.051000+02:00" + }, + { + "awsAccountId": "012345678901", + "description": "In the Linux kernel, the following vulnerability has been resolved: fpga: manager: add owner module and take its refcount The current implementation of the fpga manager assumes that the low-level module registers a driver for the parent device and uses its owner pointer to take the module's refcount. This approach is problematic since it can lead to a null pointer dereference while attempting to get the manager if the parent device does not have a driver. To address this problem, add a module owner pointer to the fpga_manager struct and use it to take the module's refcount. Modify the functions for registering the manager to take an additional owner module parameter and rename them to avoid conflicts. Use the old function names for helper macros that automatically set the module that registers the manager as the owner. This ensures compatibility with existing low-level control modules and reduces the chances of registering a manager without setting the owner. Also, update the documentation to keep it cons", + "epss": { + "score": 0.00045 + }, + "exploitAvailable": "NO", + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/00222d5d29811496c48d6e3a6f6e2bf7", + "firstObservedAt": "2024-06-27T00:56:32.023000+02:00", + "fixAvailable": "NO", + "lastObservedAt": "2024-06-27T00:56:32.023000+02:00", + "packageVulnerabilityDetails": { + "cvss": [], + "referenceUrls": [], + "relatedVulnerabilities": [], + "source": "DEBIAN_CVE", + "sourceUrl": "https://security-tracker.debian.org/tracker/CVE-2024-37021", + "vendorSeverity": "not yet assigned", + "vulnerabilityId": "CVE-2024-37021", + "vulnerablePackages": [ + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "NotAvailable", + "name": "linux", + "packageManager": "OS", + "release": "2", + "remediation": "NotAvailable", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "4.19.118" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEcrContainerImage": { + "architecture": "amd64", + "author": "John D ", + "imageHash": "sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "imageTags": [ + "2.3.0" + ], + "platform": "DEBIAN_10", + "pushedAt": "2020-06-10T11:30:35+02:00", + "registry": "012345678901", + "repositoryName": "my-awesome-docker-image" + } + }, + "id": "arn:aws:ecr:us-east-1:012345678901:repository/my-awesome-docker-image/sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "partition": "aws", + "region": "us-east-1", + "tags": {}, + "type": "AWS_ECR_CONTAINER_IMAGE" + } + ], + "severity": "UNTRIAGED", + "status": "ACTIVE", + "title": "CVE-2024-37021 - linux", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-06-27T00:56:32.023000+02:00" + }, + { + "awsAccountId": "012345678901", + "description": "A flaw use-after-free in function sco_sock_sendmsg() of the Linux kernel HCI subsystem was found in the way user calls ioct UFFDIO_REGISTER or other way triggers race condition of the call sco_conn_del() together with the call sco_sock_sendmsg() with the expected controllable faulting memory page. A privileged local user could use this flaw to crash the system or escalate their privileges on the system.", + "epss": { + "score": 0.00042 + }, + "exploitAvailable": "NO", + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/00264176c0e6a64e6465830320e8ccd9", + "firstObservedAt": "2024-02-02T16:30:33.045000+01:00", + "fixAvailable": "YES", + "inspectorScore": 7.0, + "inspectorScoreDetails": { + "adjustedCvss": { + "adjustments": [], + "cvssSource": "NVD", + "score": 7.0, + "scoreSource": "NVD", + "scoringVector": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "version": "3.1" + } + }, + "lastObservedAt": "2024-06-14T04:03:53.051000+02:00", + "packageVulnerabilityDetails": { + "cvss": [ + { + "baseScore": 7.0, + "scoringVector": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "source": "NVD", + "version": "3.1" + } + ], + "referenceUrls": [], + "relatedVulnerabilities": [], + "source": "DEBIAN_CVE", + "sourceUrl": "https://security-tracker.debian.org/tracker/CVE-2021-3640", + "vendorSeverity": "not yet assigned", + "vulnerabilityId": "CVE-2021-3640", + "vulnerablePackages": [ + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "0:4.19.232-1", + "name": "linux", + "packageManager": "OS", + "release": "2", + "remediation": "apt-get update && apt-get upgrade", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "4.19.118" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEcrContainerImage": { + "architecture": "amd64", + "author": "John D ", + "imageHash": "sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "imageTags": [ + "2.3.0" + ], + "platform": "DEBIAN_10", + "pushedAt": "2020-06-10T11:30:35+02:00", + "registry": "012345678901", + "repositoryName": "my-awesome-docker-image" + } + }, + "id": "arn:aws:ecr:us-east-1:012345678901:repository/my-awesome-docker-image/sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "partition": "aws", + "region": "us-east-1", + "tags": {}, + "type": "AWS_ECR_CONTAINER_IMAGE" + } + ], + "severity": "HIGH", + "status": "ACTIVE", + "title": "CVE-2021-3640 - linux", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-06-14T04:03:53.051000+02:00" + }, + { + "awsAccountId": "012345678901", + "description": "In the Linux kernel, the following vulnerability has been resolved: net:emac/emac-mac: Fix a use after free in emac_mac_tx_buf_send In emac_mac_tx_buf_send, it calls emac_tx_fill_tpd(..,skb,..). If some error happens in emac_tx_fill_tpd(), the skb will be freed via dev_kfree_skb(skb) in error branch of emac_tx_fill_tpd(). But the freed skb is still used via skb->len by netdev_sent_queue(,skb->len). As i observed that emac_tx_fill_tpd() haven't modified the value of skb->len, thus my patch assigns skb->len to 'len' before the possible free and use 'len' instead of skb->len later.", + "epss": { + "score": 0.00044 + }, + "exploitAvailable": "NO", + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/0027f58875add9818da30aaf4d8d69a3", + "firstObservedAt": "2024-03-05T00:56:23.134000+01:00", + "fixAvailable": "YES", + "lastObservedAt": "2024-06-14T04:03:53.051000+02:00", + "packageVulnerabilityDetails": { + "cvss": [], + "referenceUrls": [], + "relatedVulnerabilities": [], + "source": "DEBIAN_CVE", + "sourceUrl": "https://security-tracker.debian.org/tracker/CVE-2021-47013", + "vendorSeverity": "not yet assigned", + "vulnerabilityId": "CVE-2021-47013", + "vulnerablePackages": [ + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "0:4.19.194-1", + "name": "linux", + "packageManager": "OS", + "release": "2", + "remediation": "apt-get update && apt-get upgrade", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "4.19.118" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEcrContainerImage": { + "architecture": "amd64", + "author": "John D ", + "imageHash": "sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "imageTags": [ + "2.3.0" + ], + "platform": "DEBIAN_10", + "pushedAt": "2020-06-10T11:30:35+02:00", + "registry": "012345678901", + "repositoryName": "my-awesome-docker-image" + } + }, + "id": "arn:aws:ecr:us-east-1:012345678901:repository/my-awesome-docker-image/sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "partition": "aws", + "region": "us-east-1", + "tags": {}, + "type": "AWS_ECR_CONTAINER_IMAGE" + } + ], + "severity": "UNTRIAGED", + "status": "ACTIVE", + "title": "CVE-2021-47013 - linux", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-06-14T04:03:53.051000+02:00" + }, + { + "awsAccountId": "012345678901", + "description": "In libxml2 before 2.9.14, several buffer handling functions in buf.c (xmlBuf*) and tree.c (xmlBuffer*) don't check for integer overflows. This can result in out-of-bounds memory writes. Exploitation requires a victim to open a crafted, multi-gigabyte XML file. Other software using libxml2's buffer functions, for example libxslt through 1.1.35, is affected as well.", + "epss": { + "score": 0.0015 + }, + "exploitAvailable": "NO", + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/00657fce9f7e60372fcfda10e636c2b0", + "firstObservedAt": "2024-02-02T16:30:33.045000+01:00", + "fixAvailable": "YES", + "inspectorScore": 6.5, + "inspectorScoreDetails": { + "adjustedCvss": { + "adjustments": [], + "cvssSource": "NVD", + "score": 6.5, + "scoreSource": "NVD", + "scoringVector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "version": "3.1" + } + }, + "lastObservedAt": "2024-02-02T16:30:33.045000+01:00", + "packageVulnerabilityDetails": { + "cvss": [ + { + "baseScore": 6.5, + "scoringVector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "source": "NVD", + "version": "3.1" + } + ], + "referenceUrls": [ + "https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1010526" + ], + "relatedVulnerabilities": [], + "source": "DEBIAN_CVE", + "sourceUrl": "https://security-tracker.debian.org/tracker/CVE-2022-29824", + "vendorSeverity": "not yet assigned", + "vulnerabilityId": "CVE-2022-29824", + "vulnerablePackages": [ + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "0:2.9.4+dfsg1-7+deb10u4", + "name": "libxml2", + "packageManager": "OS", + "release": "7+b3", + "remediation": "apt-get update && apt-get upgrade", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "2.9.4+dfsg1" + }, + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "0:2.9.4+dfsg1-7+deb10u4", + "name": "libxml2", + "packageManager": "OS", + "release": "7", + "remediation": "apt-get update && apt-get upgrade", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "2.9.4+dfsg1" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEcrContainerImage": { + "architecture": "amd64", + "author": "John D ", + "imageHash": "sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "imageTags": [ + "2.3.0" + ], + "platform": "DEBIAN_10", + "pushedAt": "2020-06-10T11:30:35+02:00", + "registry": "012345678901", + "repositoryName": "my-awesome-docker-image" + } + }, + "id": "arn:aws:ecr:us-east-1:012345678901:repository/my-awesome-docker-image/sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "partition": "aws", + "region": "us-east-1", + "tags": {}, + "type": "AWS_ECR_CONTAINER_IMAGE" + } + ], + "severity": "MEDIUM", + "status": "ACTIVE", + "title": "CVE-2022-29824 - libxml2, libxml2", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-02-02T16:30:33.045000+01:00" + }, + { + "awsAccountId": "012345678901", + "description": "An array indexing vulnerability was found in the netfilter subsystem of the Linux kernel. A missing macro could lead to a miscalculation of the `h->nets` array offset, providing attackers with the primitive to arbitrarily increment/decrement a memory buffer out-of-bound. This issue may allow a local user to crash the system or potentially escalate their privileges on the system.", + "epss": { + "score": 0.00044 + }, + "exploitAvailable": "YES", + "exploitabilityDetails": { + "lastKnownExploitAt": "2024-07-30T09:04:56+02:00" + }, + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/00679d1d8da777d13bdd24b20c403aac", + "firstObservedAt": "2024-02-02T16:30:33.045000+01:00", + "fixAvailable": "YES", + "inspectorScore": 7.8, + "inspectorScoreDetails": { + "adjustedCvss": { + "adjustments": [], + "cvssSource": "NVD", + "score": 7.8, + "scoreSource": "NVD", + "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "version": "3.1" + } + }, + "lastObservedAt": "2024-07-31T17:59:32+02:00", + "packageVulnerabilityDetails": { + "cvss": [ + { + "baseScore": 7.8, + "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "source": "NVD", + "version": "3.1" + } + ], + "referenceUrls": [], + "relatedVulnerabilities": [], + "source": "DEBIAN_CVE", + "sourceUrl": "https://security-tracker.debian.org/tracker/CVE-2023-42753", + "vendorSeverity": "not yet assigned", + "vulnerabilityId": "CVE-2023-42753", + "vulnerablePackages": [ + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "0:4.19.304-1", + "name": "linux", + "packageManager": "OS", + "release": "2", + "remediation": "apt-get update && apt-get upgrade", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "4.19.118" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEcrContainerImage": { + "architecture": "amd64", + "author": "John D ", + "imageHash": "sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "imageTags": [ + "2.3.0" + ], + "platform": "DEBIAN_10", + "pushedAt": "2020-06-10T11:30:35+02:00", + "registry": "012345678901", + "repositoryName": "my-awesome-docker-image" + } + }, + "id": "arn:aws:ecr:us-east-1:012345678901:repository/my-awesome-docker-image/sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "partition": "aws", + "region": "us-east-1", + "tags": {}, + "type": "AWS_ECR_CONTAINER_IMAGE" + } + ], + "severity": "HIGH", + "status": "ACTIVE", + "title": "CVE-2023-42753 - linux", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-07-31T17:59:32+02:00" + }, + { + "awsAccountId": "012345678901", + "description": "An issue was discovered in the Linux kernel before 5.11.8. kernel/bpf/verifier.c performs undesirable out-of-bounds speculation on pointer arithmetic, leading to side-channel attacks that defeat Spectre mitigations and obtain sensitive information from kernel memory, aka CID-f232326f6966. This affects pointer types that do not define a ptr_limit.", + "epss": { + "score": 0.00047 + }, + "exploitAvailable": "YES", + "exploitabilityDetails": { + "lastKnownExploitAt": "2024-07-15T18:13:11+02:00" + }, + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/0068a17c3e52bcf1bf60921d799fae20", + "firstObservedAt": "2024-02-02T16:30:33.045000+01:00", + "fixAvailable": "YES", + "inspectorScore": 4.7, + "inspectorScoreDetails": { + "adjustedCvss": { + "adjustments": [], + "cvssSource": "NVD", + "score": 4.7, + "scoreSource": "NVD", + "scoringVector": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + "version": "3.1" + } + }, + "lastObservedAt": "2024-07-31T17:59:32+02:00", + "packageVulnerabilityDetails": { + "cvss": [ + { + "baseScore": 4.7, + "scoringVector": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + "source": "NVD", + "version": "3.1" + } + ], + "referenceUrls": [], + "relatedVulnerabilities": [], + "source": "DEBIAN_CVE", + "sourceUrl": "https://security-tracker.debian.org/tracker/CVE-2020-27170", + "vendorSeverity": "unimportant", + "vulnerabilityId": "CVE-2020-27170", + "vulnerablePackages": [ + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "0:4.19.181-1", + "name": "linux", + "packageManager": "OS", + "release": "2", + "remediation": "apt-get update && apt-get upgrade", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "4.19.118" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEcrContainerImage": { + "architecture": "amd64", + "author": "John D ", + "imageHash": "sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "imageTags": [ + "2.3.0" + ], + "platform": "DEBIAN_10", + "pushedAt": "2020-06-10T11:30:35+02:00", + "registry": "012345678901", + "repositoryName": "my-awesome-docker-image" + } + }, + "id": "arn:aws:ecr:us-east-1:012345678901:repository/my-awesome-docker-image/sha256:d667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "partition": "aws", + "region": "us-east-1", + "tags": {}, + "type": "AWS_ECR_CONTAINER_IMAGE" + } + ], + "severity": "MEDIUM", + "status": "ACTIVE", + "title": "CVE-2020-27170 - linux", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-07-31T17:59:32+02:00" + }, + { + "awsAccountId": "012345678901", + "description": "The xz_decomp function in xzlib.c in libxml2 2.9.1 does not properly detect compression errors, which allows context-dependent attackers to cause a denial of service (process hang) via crafted XML data.", + "epss": { + "score": 0.0097 + }, + "exploitAvailable": "NO", + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/000e155e74c3ccb3b2e13d08a65ccafc", + "firstObservedAt": "2023-07-30T11:11:39.233000+02:00", + "fixAvailable": "YES", + "lastObservedAt": "2024-03-19T15:31:08.006000+01:00", + "packageVulnerabilityDetails": { + "cvss": [ + { + "baseScore": 2.6, + "scoringVector": "AV:N/AC:H/Au:N/C:N/I:N/A:P", + "source": "NVD", + "version": "2.0" + } + ], + "referenceUrls": [ + "https://security.gentoo.org/glsa/201701-37", + "https://support.apple.com/HT206168", + "https://support.apple.com/HT206167", + "https://bugzilla.gnome.org/show_bug.cgi?id=757466", + "https://support.apple.com/HT206166", + "https://support.apple.com/HT206169" + ], + "relatedVulnerabilities": [], + "source": "NVD", + "sourceUrl": "https://nvd.nist.gov/vuln/detail/CVE-2015-8035", + "vendorCreatedAt": "2015-11-18T17:59:09+01:00", + "vendorSeverity": "LOW", + "vendorUpdatedAt": "2019-03-08T17:06:36+01:00", + "vulnerabilityId": "CVE-2015-8035", + "vulnerablePackages": [ + { + "arch": "X86_64", + "epoch": 0, + "fixedInVersion": "0:2.9.1-6.el7.4", + "name": "libxml2", + "packageManager": "OS", + "release": "6.el7_2.3", + "remediation": "yum update libxml2", + "version": "2.9.1" + }, + { + "arch": "X86_64", + "epoch": 0, + "fixedInVersion": "0:2.9.1-6.el7.4", + "name": "libxml2-python", + "packageManager": "OS", + "release": "6.el7_2.3", + "remediation": "yum update libxml2-python", + "version": "2.9.1" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEc2Instance": { + "iamInstanceProfileArn": "arn:aws:iam::012345678901:instance-profile/AmazonEC2RoleforSSM", + "imageId": "ami-01234567890abcdef", + "ipV4Addresses": [ + "8.84.11.16", + "10.52.88.71" + ], + "ipV6Addresses": [], + "keyName": "my-key", + "launchedAt": "2023-07-30T17:07:50+02:00", + "platform": "CENTOS_7", + "subnetId": "subnet-01234567890abcdef", + "type": "t3a.2xlarge", + "vpcId": "vpc-01234567890abcdef" + } + }, + "id": "i-0b22a22eec53b9321", + "partition": "aws", + "region": "us-east-1", + "tags": { + "Backup_Policy": "Monthly", + "Name": "my-instance" + }, + "type": "AWS_EC2_INSTANCE" + } + ], + "severity": "LOW", + "status": "ACTIVE", + "title": "CVE-2015-8035 - libxml2, libxml2-python", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-03-19T15:31:08.006000+01:00" + } + ] +} \ No newline at end of file diff --git a/unittests/scans/aws_inspector2/aws_inspector2_one_vul.json b/unittests/scans/aws_inspector2/aws_inspector2_one_vul.json new file mode 100644 index 00000000000..dac01fadd20 --- /dev/null +++ b/unittests/scans/aws_inspector2/aws_inspector2_one_vul.json @@ -0,0 +1,89 @@ +{ + "findings": [ + { + "awsAccountId": "012345678901", + "description": "A memory leak flaw was found in the Linux kernel in the ccp_run_aes_gcm_cmd() function in drivers/crypto/ccp/ccp-ops.c, which allows attackers to cause a denial of service (memory consumption). This vulnerability is similar with the older CVE-2019-18808.", + "epss": { + "score": 0.00075 + }, + "exploitAvailable": "NO", + "findingArn": "arn:aws:inspector2:us-east-1:012345678901:finding/00031f625ee77ac186869f906eb73366", + "firstObservedAt": "2024-02-02T16:30:33.045000+01:00", + "fixAvailable": "YES", + "inspectorScore": 5.5, + "inspectorScoreDetails": { + "adjustedCvss": { + "adjustments": [], + "cvssSource": "NVD", + "score": 5.5, + "scoreSource": "NVD", + "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + "version": "3.1" + } + }, + "lastObservedAt": "2024-06-14T04:03:53.051000+02:00", + "packageVulnerabilityDetails": { + "cvss": [ + { + "baseScore": 5.5, + "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + "source": "NVD", + "version": "3.1" + } + ], + "referenceUrls": [], + "relatedVulnerabilities": [], + "source": "DEBIAN_CVE", + "sourceUrl": "https://security-tracker.debian.org/tracker/CVE-2021-3744", + "vendorSeverity": "unimportant", + "vulnerabilityId": "CVE-2021-3744", + "vulnerablePackages": [ + { + "arch": "AMD64", + "epoch": 0, + "fixedInVersion": "0:4.19.232-1", + "name": "linux", + "packageManager": "OS", + "release": "2", + "remediation": "apt-get update && apt-get upgrade", + "sourceLayerHash": "sha256:11a88e76431345b48525c4e4d58d497810019f4bafdb5e2bd56dc34a05e09cc3", + "version": "4.19.118" + } + ] + }, + "remediation": { + "recommendation": { + "text": "None Provided" + } + }, + "resources": [ + { + "details": { + "awsEcrContainerImage": { + "architecture": "amd64", + "author": "John D ", + "imageHash": "sha256:c667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "imageTags": [ + "2.3.0" + ], + "platform": "DEBIAN_10", + "pushedAt": "2020-06-10T11:30:35+02:00", + "registry": "012345678901", + "repositoryName": "my-awesome-docker-image" + } + }, + "id": "arn:aws:ecr:us-east-1:012345678901:repository/my-awesome-docker-image/sha256:c667ad018b538adadb5bd1db75836c8561b23bc7d31d9cd8941fa74f5df93802", + "partition": "aws", + "region": "us-east-1", + "tags": {}, + "type": "AWS_ECR_CONTAINER_IMAGE" + } + ], + "severity": "MEDIUM", + "status": "ACTIVE", + "title": "CVE-2021-3744 - linux", + "type": "PACKAGE_VULNERABILITY", + "updatedAt": "2024-06-14T04:03:53.051000+02:00" + } + ] +} \ No newline at end of file diff --git a/unittests/scans/aws_inspector2/aws_inspector2_zero_vul.json b/unittests/scans/aws_inspector2/aws_inspector2_zero_vul.json new file mode 100644 index 00000000000..1ed172b30bc --- /dev/null +++ b/unittests/scans/aws_inspector2/aws_inspector2_zero_vul.json @@ -0,0 +1,3 @@ +{ + "findings": [] +} \ No newline at end of file diff --git a/unittests/scans/aws_inspector2/empty_with_error.json b/unittests/scans/aws_inspector2/empty_with_error.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/unittests/scans/aws_inspector2/empty_with_error.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/unittests/tools/test_aws_inspector2_parser.py b/unittests/tools/test_aws_inspector2_parser.py new file mode 100644 index 00000000000..f023bec88a2 --- /dev/null +++ b/unittests/tools/test_aws_inspector2_parser.py @@ -0,0 +1,46 @@ +from django.test import TestCase + +from dojo.models import Test +from dojo.tools.aws_inspector2.parser import AWSInspector2Parser + + +class TestAWSInspector2Parser(TestCase): + + def test_aws_inspector2_parser_with_no_vuln_has_no_findings(self): + with open("unittests/scans/aws_inspector2/aws_inspector2_zero_vul.json", encoding="utf-8") as testfile: + parser = AWSInspector2Parser() + findings = parser.get_findings(testfile, Test()) + testfile.close() + self.assertEqual(0, len(findings)) + + def test_aws_inspector2_parser_with_one_vuln_has_one_findings(self): + with open("unittests/scans/aws_inspector2/aws_inspector2_one_vul.json", encoding="utf-8") as testfile: + parser = AWSInspector2Parser() + findings = parser.get_findings(testfile, Test()) + testfile.close() + for finding in findings: + for endpoint in finding.unsaved_endpoints: + endpoint.clean() + self.assertEqual(1, len(findings)) + self.assertEqual("CVE-2021-3744 - linux", findings[0].title) + self.assertEqual("Medium", findings[0].severity) + + def test_aws_inspector2_parser_with_many_vuln_has_many_findings(self): + with open("unittests/scans/aws_inspector2/aws_inspector2_many_vul.json", encoding="utf-8") as testfile: + parser = AWSInspector2Parser() + findings = parser.get_findings(testfile, Test()) + testfile.close() + for finding in findings: + for endpoint in finding.unsaved_endpoints: + endpoint.clean() + self.assertEqual(8, len(findings)) + + def test_aws_inspector2_parser_empty_with_error(self): + with self.assertRaises(TypeError) as context: + with open("unittests/scans/aws_inspector2/empty_with_error.json", encoding="utf-8") as testfile: + parser = AWSInspector2Parser() + parser.get_findings(testfile, Test()) + testfile.close() + self.assertTrue( + "Incorrect Inspector2 report format" in str(context.exception), + ) From 129a42aa84be264d8c4ea653330d883dc72787e1 Mon Sep 17 00:00:00 2001 From: Harold Blankenship <36673698+hblankenship@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:15:42 -0600 Subject: [PATCH 69/74] Add toggle to dictate enforcement of verified status (#11131) * enforce verified initial changes * fix views, migrations * ruff life * poor design, None used as value * adding an if created another query? * cleaner and removes most copy+paste * add new test case * Fix jira tests * Fix ruff * Add product grade test --------- Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> --- ...ttings_enforce_verified_status_and_more.py | 23 + dojo/forms.py | 2 +- dojo/github.py | 2 +- dojo/jira_link/helper.py | 8 +- .../management/commands/jira_async_updates.py | 8 +- .../commands/push_to_jira_update.py | 5 +- dojo/metrics/utils.py | 6 +- dojo/metrics/views.py | 299 ++++-- dojo/models.py | 77 +- dojo/reports/views.py | 30 +- dojo/reports/widgets.py | 10 +- dojo/utils.py | 190 ++-- unittests/dojo_test_case.py | 20 + unittests/test_jira_import_and_pushing_api.py | 20 +- unittests/test_metrics_queries.py | 2 +- unittests/test_product_grading.py | 63 ++ ...a_not_verified_with_enforced_verified.yaml | 79 ++ ...ot_verified_without_enforced_verified.yaml | 941 ++++++++++++++++++ 18 files changed, 1566 insertions(+), 219 deletions(-) create mode 100644 dojo/db_migrations/0218_system_settings_enforce_verified_status_and_more.py create mode 100644 unittests/test_product_grading.py create mode 100644 unittests/vcr/jira/JIRAImportAndPushTestApi.test_import_with_push_to_jira_not_verified_with_enforced_verified.yaml create mode 100644 unittests/vcr/jira/JIRAImportAndPushTestApi.test_import_with_push_to_jira_not_verified_without_enforced_verified.yaml diff --git a/dojo/db_migrations/0218_system_settings_enforce_verified_status_and_more.py b/dojo/db_migrations/0218_system_settings_enforce_verified_status_and_more.py new file mode 100644 index 00000000000..beec72caffa --- /dev/null +++ b/dojo/db_migrations/0218_system_settings_enforce_verified_status_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.9 on 2024-10-22 19:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0217_jira_project_enabled'), + ] + + operations = [ + migrations.AddField( + model_name='system_settings', + name='enforce_verified_status', + field=models.BooleanField(default=True, help_text='When enabled, features such as product grading, jira integration, metrics, and reports will only interact with verified findings.', verbose_name='Enforce Verified Status'), + ), + migrations.AlterField( + model_name='jira_project', + name='push_all_issues', + field=models.BooleanField(blank=True, default=False, help_text='Automatically create JIRA tickets for verified findings, assuming enforce_verified_status is True, or for all findings otherwise. Once linked, the JIRA ticket will continue to sync, regardless of status in DefectDojo.'), + ), + ] diff --git a/dojo/forms.py b/dojo/forms.py index 2d58e0cd423..3577a9ff2ab 100644 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -3076,7 +3076,7 @@ def clean(self): elif self.cleaned_data.get("push_to_jira", None): active = self.finding_form["active"].value() verified = self.finding_form["verified"].value() - if not active or not verified: + if not active or (not verified and get_system_setting("enforce_verified_status", True)): logger.debug("Findings must be active and verified to be pushed to JIRA") error_message = "Findings must be active and verified to be pushed to JIRA" self.add_error("push_to_jira", ValidationError(error_message, code="not_active_or_verified")) diff --git a/dojo/github.py b/dojo/github.py index 5fe1ca35c13..dc1f865b128 100644 --- a/dojo/github.py +++ b/dojo/github.py @@ -123,7 +123,7 @@ def add_external_issue_github(find, prod, eng): github_conf = github_pkey.git_conf # We push only active and verified issues - if "Active" in find.status() and "Verified" in find.status(): + if "Active" in find.status() and ("Verified" in find.status() and get_system_setting("enforce_verified_status", True)): eng = Engagement.objects.get(test=find.test) prod = Product.objects.get(engagement=eng) github_product_key = GITHUB_PKey.objects.get(product=prod) diff --git a/dojo/jira_link/helper.py b/dojo/jira_link/helper.py index 860f4f01d1b..f1ce769fb26 100644 --- a/dojo/jira_link/helper.py +++ b/dojo/jira_link/helper.py @@ -145,9 +145,11 @@ def can_be_pushed_to_jira(obj, form=None): logger.debug("can_be_pushed_to_jira: %s, %s, %s", active, verified, severity) - if not active or not verified: - logger.debug("Findings must be active and verified to be pushed to JIRA") - return False, "Findings must be active and verified to be pushed to JIRA", "not_active_or_verified" + isenforced = get_system_setting("enforce_verified_status", True) + + if not active or (not verified and isenforced): + logger.debug("Findings must be active and verified, if enforced by system settings, to be pushed to JIRA") + return False, "Findings must be active and verified, if enforced by system settings, to be pushed to JIRA", "not_active_or_verified" jira_minimum_threshold = None if System_Settings.objects.get().jira_minimum_severity: diff --git a/dojo/management/commands/jira_async_updates.py b/dojo/management/commands/jira_async_updates.py index 78ea8cef6c1..a49ae04050a 100644 --- a/dojo/management/commands/jira_async_updates.py +++ b/dojo/management/commands/jira_async_updates.py @@ -1,11 +1,11 @@ import logging from django.core.management.base import BaseCommand -from django.utils import timezone from jira.exceptions import JIRAError import dojo.jira_link.helper as jira_helper from dojo.models import Dojo_User, Finding, Notes, User +from dojo.utils import get_system_setting, timezone """ Author: Aaron Weaver @@ -22,7 +22,11 @@ class Command(BaseCommand): def handle(self, *args, **options): findings = Finding.objects.exclude(jira_issue__isnull=True) - findings = findings.filter(verified=True, active=True) + if get_system_setting("enforce_verified_status", True): + findings = findings.filter(verified=True, active=True) + else: + findings = findings.filter(active=True) + findings = findings.prefetch_related("jira_issue") # finding = Finding.objects.get(id=1) for finding in findings: diff --git a/dojo/management/commands/push_to_jira_update.py b/dojo/management/commands/push_to_jira_update.py index c44fbe68f19..164bc8e9704 100644 --- a/dojo/management/commands/push_to_jira_update.py +++ b/dojo/management/commands/push_to_jira_update.py @@ -23,7 +23,10 @@ class Command(BaseCommand): def handle(self, *args, **options): findings = Finding.objects.exclude(jira_issue__isnull=True) - findings = findings.filter(verified=True, active=True) + if get_system_setting("enforce_verified_status", True): + findings = findings.filter(verified=True, active=True) + else: + findings = findings.filter(active=True) for finding in findings: logger.info("Checking issue:" + str(finding.id)) diff --git a/dojo/metrics/utils.py b/dojo/metrics/utils.py index a6b947d2b97..9a4b9f6bb04 100644 --- a/dojo/metrics/utils.py +++ b/dojo/metrics/utils.py @@ -109,8 +109,10 @@ def finding_queries( weekly_counts = query_counts_for_period(MetricsPeriod.WEEK, weeks_between) top_ten = get_authorized_products(Permissions.Product_View) - top_ten = top_ten.filter(engagement__test__finding__verified=True, - engagement__test__finding__false_p=False, + if get_system_setting("enforce_verified_status", True): + top_ten = top_ten.filter(engagement__test__finding__verified=True) + + top_ten = top_ten.filter(engagement__test__finding__false_p=False, engagement__test__finding__duplicate=False, engagement__test__finding__out_of_scope=False, engagement__test__finding__mitigated__isnull=True, diff --git a/dojo/metrics/views.py b/dojo/metrics/views.py index 631130b58da..85d9654c6ca 100644 --- a/dojo/metrics/views.py +++ b/dojo/metrics/views.py @@ -190,13 +190,17 @@ def simple_metrics(request): findings_broken_out = {} total = Finding.objects.filter(test__engagement__product__prod_type=pt, - verified=True, false_p=False, duplicate=False, out_of_scope=False, date__month=now.month, date__year=now.year, - ).distinct() + ) + + if get_system_setting("enforce_verified_status", True): + total = total.filter(verified=True) + + total = total.distinct() for f in total: if f.severity == "Critical": @@ -304,53 +308,99 @@ def product_type_counts(request): then=Value(1)), output_field=IntegerField())))["total"] - overall_in_pt = Finding.objects.filter(date__lt=end_date, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated__isnull=True, - test__engagement__product__prod_type=pt, - severity__in=("Critical", "High", "Medium", "Low")).values( - "numerical_severity").annotate(Count("numerical_severity")).order_by("numerical_severity") - - total_overall_in_pt = Finding.objects.filter(date__lte=end_date, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated__isnull=True, - test__engagement__product__prod_type=pt, - severity__in=("Critical", "High", "Medium", "Low")).aggregate( - total=Sum( - Case(When(severity__in=("Critical", "High", "Medium", "Low"), - then=Value(1)), - output_field=IntegerField())))["total"] + if get_system_setting("enforce_verified_status", True): + overall_in_pt = Finding.objects.filter(date__lt=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__prod_type=pt, + severity__in=("Critical", "High", "Medium", "Low")).values( + "numerical_severity").annotate(Count("numerical_severity")).order_by("numerical_severity") + + total_overall_in_pt = Finding.objects.filter(date__lte=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__prod_type=pt, + severity__in=("Critical", "High", "Medium", "Low")).aggregate( + total=Sum( + Case(When(severity__in=("Critical", "High", "Medium", "Low"), + then=Value(1)), + output_field=IntegerField())))["total"] + + all_current_in_pt = Finding.objects.filter(date__lte=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__prod_type=pt, + severity__in=( + "Critical", "High", "Medium", "Low")).prefetch_related( + "test__engagement__product", + "test__engagement__product__prod_type", + "test__engagement__risk_acceptance", + "reporter").order_by( + "numerical_severity") + + top_ten = Product.objects.filter(engagement__test__finding__date__lte=end_date, + engagement__test__finding__verified=True, + engagement__test__finding__false_p=False, + engagement__test__finding__duplicate=False, + engagement__test__finding__out_of_scope=False, + engagement__test__finding__mitigated__isnull=True, + engagement__test__finding__severity__in=( + "Critical", "High", "Medium", "Low"), + prod_type=pt) + else: + overall_in_pt = Finding.objects.filter(date__lt=end_date, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__prod_type=pt, + severity__in=("Critical", "High", "Medium", "Low")).values( + "numerical_severity").annotate(Count("numerical_severity")).order_by("numerical_severity") + + total_overall_in_pt = Finding.objects.filter(date__lte=end_date, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__prod_type=pt, + severity__in=("Critical", "High", "Medium", "Low")).aggregate( + total=Sum( + Case(When(severity__in=("Critical", "High", "Medium", "Low"), + then=Value(1)), + output_field=IntegerField())))["total"] + + all_current_in_pt = Finding.objects.filter(date__lte=end_date, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__prod_type=pt, + severity__in=( + "Critical", "High", "Medium", "Low")).prefetch_related( + "test__engagement__product", + "test__engagement__product__prod_type", + "test__engagement__risk_acceptance", + "reporter").order_by( + "numerical_severity") + + top_ten = Product.objects.filter(engagement__test__finding__date__lte=end_date, + engagement__test__finding__false_p=False, + engagement__test__finding__duplicate=False, + engagement__test__finding__out_of_scope=False, + engagement__test__finding__mitigated__isnull=True, + engagement__test__finding__severity__in=( + "Critical", "High", "Medium", "Low"), + prod_type=pt) - all_current_in_pt = Finding.objects.filter(date__lte=end_date, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated__isnull=True, - test__engagement__product__prod_type=pt, - severity__in=( - "Critical", "High", "Medium", "Low")).prefetch_related( - "test__engagement__product", - "test__engagement__product__prod_type", - "test__engagement__risk_acceptance", - "reporter").order_by( - "numerical_severity") - - top_ten = Product.objects.filter(engagement__test__finding__date__lte=end_date, - engagement__test__finding__verified=True, - engagement__test__finding__false_p=False, - engagement__test__finding__duplicate=False, - engagement__test__finding__out_of_scope=False, - engagement__test__finding__mitigated__isnull=True, - engagement__test__finding__severity__in=( - "Critical", "High", "Medium", "Low"), - prod_type=pt) top_ten = severity_count(top_ten, "annotate", "engagement__test__finding__severity").order_by("-critical", "-high", "-medium", "-low")[:10] cip = {"S0": 0, @@ -459,56 +509,105 @@ def product_tag_counts(request): then=Value(1)), output_field=IntegerField())))["total"] - overall_in_pt = Finding.objects.filter(date__lt=end_date, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated__isnull=True, - test__engagement__product__tags__name=pt, - test__engagement__product__in=prods, - severity__in=("Critical", "High", "Medium", "Low")).values( - "numerical_severity").annotate(Count("numerical_severity")).order_by("numerical_severity") - - total_overall_in_pt = Finding.objects.filter(date__lte=end_date, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated__isnull=True, - test__engagement__product__tags__name=pt, - test__engagement__product__in=prods, - severity__in=("Critical", "High", "Medium", "Low")).aggregate( - total=Sum( - Case(When(severity__in=("Critical", "High", "Medium", "Low"), - then=Value(1)), - output_field=IntegerField())))["total"] + if get_system_setting("enforce_verified_status", True): + overall_in_pt = Finding.objects.filter(date__lt=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=("Critical", "High", "Medium", "Low")).values( + "numerical_severity").annotate(Count("numerical_severity")).order_by("numerical_severity") + + total_overall_in_pt = Finding.objects.filter(date__lte=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=("Critical", "High", "Medium", "Low")).aggregate( + total=Sum( + Case(When(severity__in=("Critical", "High", "Medium", "Low"), + then=Value(1)), + output_field=IntegerField())))["total"] + + all_current_in_pt = Finding.objects.filter(date__lte=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=( + "Critical", "High", "Medium", "Low")).prefetch_related( + "test__engagement__product", + "test__engagement__product__prod_type", + "test__engagement__risk_acceptance", + "reporter").order_by( + "numerical_severity") + + top_ten = Product.objects.filter(engagement__test__finding__date__lte=end_date, + engagement__test__finding__verified=True, + engagement__test__finding__false_p=False, + engagement__test__finding__duplicate=False, + engagement__test__finding__out_of_scope=False, + engagement__test__finding__mitigated__isnull=True, + engagement__test__finding__severity__in=( + "Critical", "High", "Medium", "Low"), + tags__name=pt, engagement__product__in=prods) + else: + overall_in_pt = Finding.objects.filter(date__lt=end_date, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=("Critical", "High", "Medium", "Low")).values( + "numerical_severity").annotate(Count("numerical_severity")).order_by("numerical_severity") + + total_overall_in_pt = Finding.objects.filter(date__lte=end_date, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=("Critical", "High", "Medium", "Low")).aggregate( + total=Sum( + Case(When(severity__in=("Critical", "High", "Medium", "Low"), + then=Value(1)), + output_field=IntegerField())))["total"] + + all_current_in_pt = Finding.objects.filter(date__lte=end_date, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=( + "Critical", "High", "Medium", "Low")).prefetch_related( + "test__engagement__product", + "test__engagement__product__prod_type", + "test__engagement__risk_acceptance", + "reporter").order_by( + "numerical_severity") + + top_ten = Product.objects.filter(engagement__test__finding__date__lte=end_date, + engagement__test__finding__false_p=False, + engagement__test__finding__duplicate=False, + engagement__test__finding__out_of_scope=False, + engagement__test__finding__mitigated__isnull=True, + engagement__test__finding__severity__in=( + "Critical", "High", "Medium", "Low"), + tags__name=pt, engagement__product__in=prods) - all_current_in_pt = Finding.objects.filter(date__lte=end_date, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated__isnull=True, - test__engagement__product__tags__name=pt, - test__engagement__product__in=prods, - severity__in=( - "Critical", "High", "Medium", "Low")).prefetch_related( - "test__engagement__product", - "test__engagement__product__prod_type", - "test__engagement__risk_acceptance", - "reporter").order_by( - "numerical_severity") - - top_ten = Product.objects.filter(engagement__test__finding__date__lte=end_date, - engagement__test__finding__verified=True, - engagement__test__finding__false_p=False, - engagement__test__finding__duplicate=False, - engagement__test__finding__out_of_scope=False, - engagement__test__finding__mitigated__isnull=True, - engagement__test__finding__severity__in=( - "Critical", "High", "Medium", "Low"), - tags__name=pt, engagement__product__in=prods) top_ten = severity_count(top_ten, "annotate", "engagement__test__finding__severity").order_by("-critical", "-high", "-medium", "-low")[:10] cip = {"S0": 0, @@ -586,7 +685,11 @@ def view_engineer(request, eid): raise PermissionDenied now = timezone.now() - findings = Finding.objects.filter(reporter=user, verified=True) + if get_system_setting("enforce_verified_status", True): + findings = Finding.objects.filter(reporter=user, verified=True) + else: + findings = Finding.objects.filter(reporter=user) + closed_findings = Finding.objects.filter(mitigated_by=user) open_findings = findings.exclude(mitigated__isnull=False) open_month = findings.filter(date__year=now.year, date__month=now.month) diff --git a/dojo/models.py b/dojo/models.py index b4a163f1a78..dba8f45c447 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -358,6 +358,15 @@ class System_Settings(models.Model): webhooks_notifications_timeout = models.IntegerField(default=10, help_text=_("How many seconds will DefectDojo waits for response from webhook endpoint")) + enforce_verified_status = models.BooleanField( + default=True, + verbose_name=_("Enforce Verified Status"), + help_text=_("When enabled, features such as product grading, jira " + "integration, metrics, and reports will only interact " + "with verified findings.", + ), + ) + false_positive_history = models.BooleanField( default=False, help_text=_( "(EXPERIMENTAL) DefectDojo will automatically mark the finding as a " @@ -1195,42 +1204,24 @@ def endpoint_count(self): def open_findings(self, start_date=None, end_date=None): if start_date is None or end_date is None: return {} - critical = Finding.objects.filter(test__engagement__product=self, - mitigated__isnull=True, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - severity="Critical", - date__range=[start_date, - end_date]).count() - high = Finding.objects.filter(test__engagement__product=self, - mitigated__isnull=True, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - severity="High", - date__range=[start_date, - end_date]).count() - medium = Finding.objects.filter(test__engagement__product=self, + + from dojo.utils import get_system_setting + findings = Finding.objects.filter(test__engagement__product=self, mitigated__isnull=True, - verified=True, false_p=False, duplicate=False, out_of_scope=False, - severity="Medium", date__range=[start_date, - end_date]).count() - low = Finding.objects.filter(test__engagement__product=self, - mitigated__isnull=True, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - severity="Low", - date__range=[start_date, - end_date]).count() + end_date]) + + if get_system_setting("enforce_verified_status", True): + findings = findings.filter(verified=True) + + critical = findings.filter(severity="Critical").count() + high = findings.filter(severity="High").count() + medium = findings.filter(severity="Medium").count() + low = findings.filter(severity="Low").count() + return {"Critical": critical, "High": high, "Medium": medium, @@ -1543,7 +1534,13 @@ def get_breadcrumbs(self): # only used by bulk risk acceptance api @property def unaccepted_open_findings(self): - return Finding.objects.filter(risk_accepted=False, active=True, verified=True, duplicate=False, test__engagement=self) + from dojo.utils import get_system_setting + + findings = Finding.objects.filter(risk_accepted=False, active=True, duplicate=False, test__engagement=self) + if get_system_setting("enforce_verified_status", True): + findings = findings.filter(verified=True) + + return findings def accept_risks(self, accepted_risks): self.risk_acceptance.add(*accepted_risks) @@ -2113,7 +2110,12 @@ def copy(self, engagement=None): # only used by bulk risk acceptance api @property def unaccepted_open_findings(self): - return Finding.objects.filter(risk_accepted=False, active=True, verified=True, duplicate=False, test=self) + from dojo.utils import get_system_setting + findings = Finding.objects.filter(risk_accepted=False, active=True, duplicate=False, test=self) + if get_system_setting("enforce_verified_status", True): + findings = findings.filter(verified=True) + + return findings def accept_risks(self, accepted_risks): self.engagement.risk_acceptance.add(*accepted_risks) @@ -2737,7 +2739,12 @@ def delete(self, *args, **kwargs): # only used by bulk risk acceptance api @classmethod def unaccepted_open_findings(cls): - return cls.objects.filter(active=True, verified=True, duplicate=False, risk_accepted=False) + from dojo.utils import get_system_setting + results = cls.objects.filter(active=True, duplicate=False, risk_accepted=False) + if get_system_setting("enforce_verified_status", True): + results = results.filter(verified=True) + + return results @property def risk_acceptance(self): @@ -3911,7 +3918,7 @@ class JIRA_Project(models.Model): verbose_name=_("Add vulnerability Id as a JIRA label"), blank=False) push_all_issues = models.BooleanField(default=False, blank=True, - help_text=_("Automatically create JIRA tickets for verified findings. Once linked, the JIRA ticket will continue to sync, regardless of status in DefectDojo.")) + help_text=_("Automatically create JIRA tickets for verified findings, assuming enforce_verified_status is True, or for all findings otherwise. Once linked, the JIRA ticket will continue to sync, regardless of status in DefectDojo.")) enable_engagement_epic_mapping = models.BooleanField(default=False, blank=True) epic_issue_type_name = models.CharField(max_length=64, blank=True, default="Epic", help_text=_("The name of the of structure that represents an Epic")) diff --git a/dojo/reports/views.py b/dojo/reports/views.py index 667cb0bb6ac..f258db9db2f 100644 --- a/dojo/reports/views.py +++ b/dojo/reports/views.py @@ -84,11 +84,15 @@ def get_findings(self, request: HttpRequest): def get_endpoints(self, request: HttpRequest): endpoints = Endpoint.objects.filter(finding__active=True, - finding__verified=True, finding__false_p=False, finding__duplicate=False, finding__out_of_scope=False, - ).distinct() + ) + if get_system_setting("enforce_verified_status", True): + endpoints = endpoints.filter(finding__active=True) + + endpoints = endpoints.distinct() + filter_string_matching = get_system_setting("filter_string_matching", False) filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter return filter_class(request.GET, queryset=endpoints, user=request.user) @@ -186,12 +190,14 @@ def report_findings(request): def report_endpoints(request): endpoints = Endpoint.objects.filter(finding__active=True, - finding__verified=True, finding__false_p=False, finding__duplicate=False, finding__out_of_scope=False, - ).distinct() + ) + if get_system_setting("enforce_verified_status", True): + endpoints = endpoints.filter(finding__active=True) + endpoints = endpoints.distinct() endpoints = EndpointFilter(request.GET, queryset=endpoints, user=request.user) paged_endpoints = get_page_items(request, endpoints.qs, 25) @@ -260,13 +266,15 @@ def endpoint_host_report(request, eid): @user_is_authorized(Product, Permissions.Product_View, "pid") def product_endpoint_report(request, pid): product = get_object_or_404(Product.objects.all().prefetch_related("engagement_set__test_set__test_type", "engagement_set__test_set__environment"), id=pid) - endpoint_ids = Endpoint.objects.filter(product=product, - finding__active=True, - finding__verified=True, - finding__false_p=False, - finding__duplicate=False, - finding__out_of_scope=False, - ).values_list("id", flat=True) + endpoints = Endpoint.objects.filter(finding__active=True, + finding__false_p=False, + finding__duplicate=False, + finding__out_of_scope=False) + + if get_system_setting("enforce_verified_status", True): + endpoint_ids = endpoints.filter(finding__active=True).values_list("id", flat=True) + + endpoint_ids = endpoints.values_list("id", flat=True) endpoints = prefetch_related_endpoints_for_report(Endpoint.objects.filter(id__in=endpoint_ids)) endpoints = EndpointReportFilter(request.GET, queryset=endpoints) diff --git a/dojo/reports/widgets.py b/dojo/reports/widgets.py index 828adada0ac..7439a4bb8f3 100644 --- a/dojo/reports/widgets.py +++ b/dojo/reports/widgets.py @@ -367,16 +367,22 @@ def report_widget_factory(json_data=None, request=None, user=None, finding_notes host=None): selected_widgets = OrderedDict() widgets = json.loads(json_data) + for idx, widget in enumerate(widgets): if list(widget.keys())[0] == "page-break": selected_widgets[list(widget.keys())[0] + "-" + str(idx)] = PageBreak() + if list(widget.keys())[0] == "endpoint-list": endpoints = Endpoint.objects.filter(finding__active=True, - finding__verified=True, finding__false_p=False, finding__duplicate=False, finding__out_of_scope=False, - ).distinct() + ) + if get_system_setting("enforce_verified_status", True): + endpoints = endpoints.filter(finding__verified=True) + + endpoints = endpoints.distinct() + d = QueryDict(mutable=True) for item in widget.get(list(widget.keys())[0]): if item["name"] in d: diff --git a/dojo/utils.py b/dojo/utils.py index 0e340f2ce01..c57695c09d9 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -1137,71 +1137,135 @@ def opened_in_period(start_date, end_date, **kwargs): end_date.month, end_date.day, tzinfo=timezone.get_current_timezone()) - opened_in_period = Finding.objects.filter( - date__range=[start_date, end_date], - **kwargs, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated__isnull=True, - severity__in=( - "Critical", "High", "Medium", - "Low")).values("numerical_severity").annotate( - Count("numerical_severity")).order_by("numerical_severity") - total_opened_in_period = Finding.objects.filter( - date__range=[start_date, end_date], - **kwargs, - verified=True, - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated__isnull=True, - severity__in=("Critical", "High", "Medium", "Low")).aggregate( - total=Sum( - Case( - When( - severity__in=("Critical", "High", "Medium", "Low"), - then=Value(1)), - output_field=IntegerField())))["total"] - - oip = { - "S0": - 0, - "S1": - 0, - "S2": - 0, - "S3": - 0, - "Total": - total_opened_in_period, - "start_date": - start_date, - "end_date": - end_date, - "closed": - Finding.objects.filter( - mitigated__date__range=[start_date, end_date], + if get_system_setting("enforce_verified_status", True): + opened_in_period = Finding.objects.filter( + date__range=[start_date, end_date], **kwargs, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + severity__in=( + "Critical", "High", "Medium", + "Low")).values("numerical_severity").annotate( + Count("numerical_severity")).order_by("numerical_severity") + total_opened_in_period = Finding.objects.filter( + date__range=[start_date, end_date], + **kwargs, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, severity__in=("Critical", "High", "Medium", "Low")).aggregate( total=Sum( Case( When( severity__in=("Critical", "High", "Medium", "Low"), then=Value(1)), - output_field=IntegerField())))["total"], - "to_date_total": - Finding.objects.filter( - date__lte=end_date.date(), - verified=True, + output_field=IntegerField())))["total"] + + oip = { + "S0": + 0, + "S1": + 0, + "S2": + 0, + "S3": + 0, + "Total": + total_opened_in_period, + "start_date": + start_date, + "end_date": + end_date, + "closed": + Finding.objects.filter( + mitigated__date__range=[start_date, end_date], + **kwargs, + severity__in=("Critical", "High", "Medium", "Low")).aggregate( + total=Sum( + Case( + When( + severity__in=("Critical", "High", "Medium", "Low"), + then=Value(1)), + output_field=IntegerField())))["total"], + "to_date_total": + Finding.objects.filter( + date__lte=end_date.date(), + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + **kwargs, + severity__in=("Critical", "High", "Medium", "Low")).count(), + } + else: + opened_in_period = Finding.objects.filter( + date__range=[start_date, end_date], + **kwargs, false_p=False, duplicate=False, out_of_scope=False, mitigated__isnull=True, + severity__in=( + "Critical", "High", "Medium", + "Low")).values("numerical_severity").annotate( + Count("numerical_severity")).order_by("numerical_severity") + total_opened_in_period = Finding.objects.filter( + date__range=[start_date, end_date], **kwargs, - severity__in=("Critical", "High", "Medium", "Low")).count(), - } + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + severity__in=("Critical", "High", "Medium", "Low")).aggregate( + total=Sum( + Case( + When( + severity__in=("Critical", "High", "Medium", "Low"), + then=Value(1)), + output_field=IntegerField())))["total"] + + oip = { + "S0": + 0, + "S1": + 0, + "S2": + 0, + "S3": + 0, + "Total": + total_opened_in_period, + "start_date": + start_date, + "end_date": + end_date, + "closed": + Finding.objects.filter( + mitigated__date__range=[start_date, end_date], + **kwargs, + severity__in=("Critical", "High", "Medium", "Low")).aggregate( + total=Sum( + Case( + When( + severity__in=("Critical", "High", "Medium", "Low"), + then=Value(1)), + output_field=IntegerField())))["total"], + "to_date_total": + Finding.objects.filter( + date__lte=end_date.date(), + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + **kwargs, + severity__in=("Critical", "High", "Medium", "Low")).count(), + } for o in opened_in_period: oip[o["numerical_severity"]] = o["numerical_severity__count"] @@ -1518,14 +1582,18 @@ def calculate_grade(product, *args, **kwargs): if system_settings.enable_product_grade: logger.debug("calculating product grade for %s:%s", product.id, product.name) - severity_values = Finding.objects.filter( - ~Q(severity="Info"), - active=True, - duplicate=False, - verified=True, - false_p=False, - test__engagement__product=product).values("severity").annotate( - Count("numerical_severity")).order_by() + findings = Finding.objects.filter( + ~Q(severity="Info"), + active=True, + duplicate=False, + false_p=False, + test__engagement__product=product) + + if get_system_setting("enforce_verified_status", True): + findings = findings.filter(verified=True) + + severity_values = findings.values("severity").annotate( + Count("numerical_severity")).order_by() low = 0 medium = 0 diff --git a/unittests/dojo_test_case.py b/unittests/dojo_test_case.py index 39d65e113bc..d8995e5d0d3 100644 --- a/unittests/dojo_test_case.py +++ b/unittests/dojo_test_case.py @@ -2,6 +2,7 @@ import json import logging import os +from functools import wraps from itertools import chain from pprint import pformat @@ -41,6 +42,25 @@ def get_unit_tests_path(): return os.path.dirname(os.path.realpath(__file__)) +def toggle_system_setting_boolean(flag_name, value): + """Decorator to temporarily set a boolean flag in System Settings.""" + + def decorator(test_func): + @wraps(test_func) + def wrapper(*args, **kwargs): + # Set the flag to the specified value + System_Settings.objects.update(**{flag_name: value}) + try: + return test_func(*args, **kwargs) + finally: + # Reset the flag to its original state after the test + System_Settings.objects.update(**{flag_name: not value}) + + return wrapper + + return decorator + + class DojoTestUtilsMixin: def get_test_admin(self, *args, **kwargs): diff --git a/unittests/test_jira_import_and_pushing_api.py b/unittests/test_jira_import_and_pushing_api.py index a086fb579b4..eeba03f9740 100644 --- a/unittests/test_jira_import_and_pushing_api.py +++ b/unittests/test_jira_import_and_pushing_api.py @@ -9,7 +9,7 @@ from dojo.jira_link import helper as jira_helper from dojo.models import Finding, Finding_Group, JIRA_Instance, User -from .dojo_test_case import DojoVCRAPITestCase, get_unit_tests_path +from .dojo_test_case import DojoVCRAPITestCase, get_unit_tests_path, toggle_system_setting_boolean logger = logging.getLogger(__name__) @@ -556,6 +556,24 @@ def test_import_with_push_to_jira_update_tags(self): # by asserting full cassette is played we know all calls to JIRA have been made as expected self.assert_cassette_played() + @toggle_system_setting_boolean("enforce_verified_status", True) # noqa: FBT003 + def test_import_with_push_to_jira_not_verified_with_enforced_verified(self): + import0 = self.import_scan_with_params(self.zap_sample5_filename, push_to_jira=True, verified=False) + test_id = import0["test"] + # This scan file has two active findings, so we should not push either of them + self.assert_jira_issue_count_in_test(test_id, 0) + # by asserting full cassette is played we know all calls to JIRA have been made as expected + self.assert_cassette_played() + + @toggle_system_setting_boolean("enforce_verified_status", False) # noqa: FBT003 + def test_import_with_push_to_jira_not_verified_without_enforced_verified(self): + import0 = self.import_scan_with_params(self.zap_sample5_filename, push_to_jira=True, verified=False) + test_id = import0["test"] + # This scan file has two active findings, so we should not push both of them + self.assert_jira_issue_count_in_test(test_id, 2) + # by asserting full cassette is played we know all calls to JIRA have been made as expected + self.assert_cassette_played() + def test_engagement_epic_creation(self): eng = self.get_engagement(3) # Set epic_mapping to true diff --git a/unittests/test_metrics_queries.py b/unittests/test_metrics_queries.py index 68e754b6d6d..68dd1f43fee 100644 --- a/unittests/test_metrics_queries.py +++ b/unittests/test_metrics_queries.py @@ -79,7 +79,7 @@ def test_finding_queries(self, mock_timezone): mock_timezone.return_value = mock_datetime # Queries over Finding - with self.assertNumQueries(27): + with self.assertNumQueries(28): product_types = [] finding_queries = utils.finding_queries( product_types, diff --git a/unittests/test_product_grading.py b/unittests/test_product_grading.py new file mode 100644 index 00000000000..bc1aa557d34 --- /dev/null +++ b/unittests/test_product_grading.py @@ -0,0 +1,63 @@ +import uuid + +from crum import impersonate + +from dojo.models import Finding, User +from unittests.dojo_test_case import DojoTestCase, toggle_system_setting_boolean + + +class ProductGradeTest(DojoTestCase): + fixtures = ["dojo_testdata.json"] + + def run(self, result=None): + testuser = User.objects.get(username="admin") + testuser.usercontactinfo.block_execution = True + testuser.save() + + # unit tests are running without any user, which will result in actions like dedupe happening in the celery process + # this doesn't work in unittests as unittests are using an in memory sqlite database and celery can't see the data + # so we're running the test under the admin user context and set block_execution to True + with impersonate(testuser): + super().run(result) + + def create_default_data(self): + self.product = self.create_product("Product Grader") + self.engagement = self.create_engagement("engagement name", product=self.product) + self.test = self.create_test(engagement=self.engagement, scan_type="ZAP Scan") + + def setUp(self): + self.create_default_data() + self.default_finding_options = { + "description": "", + "active": True, + "test": self.test, + } + + def tearDown(self): + self.product.delete() + + def create_finding_on_test(self, severity, verified=True): + Finding.objects.create(title=str(uuid.uuid4()), severity=severity, verified=verified, **self.default_finding_options) + + def create_single_critical_and_assert_grade(self, expected_grade, verified=False): + self.assertIsNone(self.product.prod_numeric_grade) + # Add a single critical finding + self.create_finding_on_test(severity="Critical", verified=verified) + # See that the grade does not degrade at all + self.assertEqual(self.product.prod_numeric_grade, expected_grade) + + @toggle_system_setting_boolean("enforce_verified_status", True) # noqa: FBT003 + def test_grade_change_with_enforced_verified_status_and_verified_is_true(self): + self.create_single_critical_and_assert_grade(40, verified=True) + + @toggle_system_setting_boolean("enforce_verified_status", True) # noqa: FBT003 + def test_grade_dose_not_change_with_enforced_verified_status_and_verified_is_false(self): + self.create_single_critical_and_assert_grade(100, verified=False) + + @toggle_system_setting_boolean("enforce_verified_status", False) # noqa: FBT003 + def test_grade_change_without_enforced_verified_status_and_verified_is_true(self): + self.create_single_critical_and_assert_grade(40, verified=True) + + @toggle_system_setting_boolean("enforce_verified_status", False) # noqa: FBT003 + def test_grade_change_without_enforced_verified_status_and_verified_is_false(self): + self.create_single_critical_and_assert_grade(40, verified=False) diff --git a/unittests/vcr/jira/JIRAImportAndPushTestApi.test_import_with_push_to_jira_not_verified_with_enforced_verified.yaml b/unittests/vcr/jira/JIRAImportAndPushTestApi.test_import_with_push_to_jira_not_verified_with_enforced_verified.yaml new file mode 100644 index 00000000000..30176474f75 --- /dev/null +++ b/unittests/vcr/jira/JIRAImportAndPushTestApi.test_import_with_push_to_jira_not_verified_with_enforced_verified.yaml @@ -0,0 +1,79 @@ +interactions: +- request: + body: '{"description": null, "user": null, "url_ui": "http://localhost:8080/test/90", + "url_api": "http://localhost:8080/api/v2/tests/90/", "product_type": {"name": + "ebooks", "id": 2, "url_ui": "http://localhost:8080/product/type/2", "url_api": + "http://localhost:8080/api/v2/product_types/2/"}, "product": {"name": "Security + How-to", "id": 2, "url_ui": "http://localhost:8080/product/2", "url_api": "http://localhost:8080/api/v2/products/2/"}, + "engagement": {"name": "1st Quarter Engagement", "id": 1, "url_ui": "http://localhost:8080/engagement/1", + "url_api": "http://localhost:8080/api/v2/engagements/1/"}, "test": {"title": + null, "id": 90, "url_ui": "http://localhost:8080/test/90", "url_api": "http://localhost:8080/api/v2/tests/90/"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Auth: + - Token xxx + Connection: + - keep-alive + Content-Length: + - '731' + Content-Type: + - application/json + User-Agent: + - DefectDojo-2.40.0-dev + X-DefectDojo-Event: + - test_added + X-DefectDojo-Instance: + - http://localhost:8080 + method: POST + uri: http://webhook.endpoint:8080/post + response: + body: + string: "{\n \"args\": {},\n \"headers\": {\n \"Accept\": [\n \"application/json\"\n + \ ],\n \"Accept-Encoding\": [\n \"gzip, deflate\"\n ],\n \"Auth\": + [\n \"Token xxx\"\n ],\n \"Connection\": [\n \"keep-alive\"\n + \ ],\n \"Content-Length\": [\n \"731\"\n ],\n \"Content-Type\": + [\n \"application/json\"\n ],\n \"Host\": [\n \"webhook.endpoint:8080\"\n + \ ],\n \"User-Agent\": [\n \"DefectDojo-2.40.0-dev\"\n ],\n \"X-Defectdojo-Event\": + [\n \"test_added\"\n ],\n \"X-Defectdojo-Instance\": [\n \"http://localhost:8080\"\n + \ ]\n },\n \"method\": \"POST\",\n \"origin\": \"172.18.0.2:43990\",\n + \ \"url\": \"http://webhook.endpoint:8080/post\",\n \"data\": \"{\\\"description\\\": + null, \\\"user\\\": null, \\\"url_ui\\\": \\\"http://localhost:8080/test/90\\\", + \\\"url_api\\\": \\\"http://localhost:8080/api/v2/tests/90/\\\", \\\"product_type\\\": + {\\\"name\\\": \\\"ebooks\\\", \\\"id\\\": 2, \\\"url_ui\\\": \\\"http://localhost:8080/product/type/2\\\", + \\\"url_api\\\": \\\"http://localhost:8080/api/v2/product_types/2/\\\"}, \\\"product\\\": + {\\\"name\\\": \\\"Security How-to\\\", \\\"id\\\": 2, \\\"url_ui\\\": \\\"http://localhost:8080/product/2\\\", + \\\"url_api\\\": \\\"http://localhost:8080/api/v2/products/2/\\\"}, \\\"engagement\\\": + {\\\"name\\\": \\\"1st Quarter Engagement\\\", \\\"id\\\": 1, \\\"url_ui\\\": + \\\"http://localhost:8080/engagement/1\\\", \\\"url_api\\\": \\\"http://localhost:8080/api/v2/engagements/1/\\\"}, + \\\"test\\\": {\\\"title\\\": null, \\\"id\\\": 90, \\\"url_ui\\\": \\\"http://localhost:8080/test/90\\\", + \\\"url_api\\\": \\\"http://localhost:8080/api/v2/tests/90/\\\"}}\",\n \"files\": + {},\n \"form\": {},\n \"json\": {\n \"description\": null,\n \"engagement\": + {\n \"id\": 1,\n \"name\": \"1st Quarter Engagement\",\n \"url_api\": + \"http://localhost:8080/api/v2/engagements/1/\",\n \"url_ui\": \"http://localhost:8080/engagement/1\"\n + \ },\n \"product\": {\n \"id\": 2,\n \"name\": \"Security How-to\",\n + \ \"url_api\": \"http://localhost:8080/api/v2/products/2/\",\n \"url_ui\": + \"http://localhost:8080/product/2\"\n },\n \"product_type\": {\n \"id\": + 2,\n \"name\": \"ebooks\",\n \"url_api\": \"http://localhost:8080/api/v2/product_types/2/\",\n + \ \"url_ui\": \"http://localhost:8080/product/type/2\"\n },\n \"test\": + {\n \"id\": 90,\n \"title\": null,\n \"url_api\": \"http://localhost:8080/api/v2/tests/90/\",\n + \ \"url_ui\": \"http://localhost:8080/test/90\"\n },\n \"url_api\": + \"http://localhost:8080/api/v2/tests/90/\",\n \"url_ui\": \"http://localhost:8080/test/90\",\n + \ \"user\": null\n }\n}\n" + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Origin: + - '*' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 30 Oct 2024 17:54:29 GMT + Transfer-Encoding: + - chunked + status: + code: 200 + message: OK +version: 1 diff --git a/unittests/vcr/jira/JIRAImportAndPushTestApi.test_import_with_push_to_jira_not_verified_without_enforced_verified.yaml b/unittests/vcr/jira/JIRAImportAndPushTestApi.test_import_with_push_to_jira_not_verified_without_enforced_verified.yaml new file mode 100644 index 00000000000..c9793ad65e6 --- /dev/null +++ b/unittests/vcr/jira/JIRAImportAndPushTestApi.test_import_with_push_to_jira_not_verified_without_enforced_verified.yaml @@ -0,0 +1,941 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://defectdojo.atlassian.net/rest/api/2/serverInfo + response: + body: + string: !!binary | + H4sIAAAAAAAAA5yQT0/DMAzFv0uubJ37J2uXGypIA6GB1O4CQihtXBFIk6pJJ03TvjuJGLAj4JNl + /57fkw+k4Ra3oyKMvDo3WLZYCOywdcK8mYg7xa2VXEcaHZkRIe2g+P4ffIXjTrYo0L6vUQ0laofj + X4+URndqQt3i75Q7HK002sMxQBxBBPNqc/lQre/rn+1m6hvfEfYUoBnM4Nl74qDMvvcp6/0Q3Epl + JuFFzSSV+JQQ5gVJnpyGV9wFMIEkm8cwT1Z1TFlWsDiNAOACPOz11v8Bx1r252wKdVwwmrEUIkrj + b7btb3RnPIgC2izPl4BFQ5OM5kXGl+mKJr6abpULnoPoliHgl4FTweFWjjy8EDs+KXdnWh7GB6JO + HUH9sq3I8TzYo9Fhc+3axbYuyfEDAAD//wMAWrYGZyQCAAA= + headers: + Atl-Request-Id: + - 3a2eb954-5757-4efc-aa3c-43df6cef6b14 + Atl-Traceid: + - 3a2eb95457574efcaa3c43df6cef6b14 + Cache-Control: + - no-cache, no-store, no-transform + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:30 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=195,atl-edge-internal;dur=13,atl-edge-upstream;dur=182,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - c52b73af5b86375fd6285957427cfd8f + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://defectdojo.atlassian.net/rest/api/2/issue/createmeta?projectKeys=NTEST&issuetypeNames=Task&expand=projects.issuetypes.fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA+xW30/bMBD+V6o87KkibakYqlShaWwS2oSmUXiZ0OQl19Xg2Jl9adOh/u87J07s + QmGBbeyFPsWX+/Gd77uvuYmgzJlMo0mUa3UFCZqo7x8nX268AzemAFznYF0MiDnZFoi5mcRxCnMK + SNWV2mMomDGcyT0JGGswGLOcx6PYZY2HA/pRCm6TNodrWNPpdPbubEYnyTKg47nkiJTAFmRLhkyf + a0GobqLxYTk+fGT9QvIlaMPE1zpXvOSwim1DLTT3YjgYD15TzdG4HI3/bZUjw3/C1GRMCCo4PCiH + B89RsGwq7o/K/dFzVMwg5UUWbfohjyy/nsCkNkPFpVHIJXtIwSSa58iVJOubXtVrv5dyg1wm2Ms5 + JNBT895K6es9G50oScx6JIoHrsEDbC9if3i4dREtyWfMXNOpkKiZNIIhpKdbb0zxDe3TZM6EgX60 + 4KCZThbrj7AEAj3o+x2dcxCpXRf3QKtiiixjem0fNfwouAZyRF1QJpMsIGP2jcVK4QY1l99tzbVB + yKzFRW9avGfO0uxs40HAmDmGOSsEXjBRQAtY5QTYTsMOnOaN0WVIg07IvHcAzhs9vBNr681qzxph + GNsJ4yXJjRBqBWnl9MLTB9hYB55QX1XsDnpu7LRzpkHi9qhdht2zFlxW5ZpZuwR+0J9qQzNk977j + hFsWJirLlaTIald+j41pzSzXOaGiEB8fYg2SerxvvbHBHPh1xc3S1FYCW09DppYQ7WCr7WyLXV1a + u7v8YQrfyHFgbToJPbu20ozA/Wd0koHGNySGMwXMaJ0cNdrzI4DdvdQnScCfffS4aKtmH2qxVXNc + EdPtBfAsF5x03o/05Rvpbxb8f99IlWJqyJVG0J32ojDkGCxFG+y34nNjanjX+hBxClRWnwQgPOEP + horTmdQsA5ke0Z8UQonTpuCrSs+Jv9NdG1g3tGMBN/YjprygS6zNXSTstjrbEdSS1N5MmNNfznte + 9pyzl+fQdQfye7Wj71T6YX3ONVea460Ps/s6a723lM/ZfgEAAP//vFhNb4JAEP0rxoM3oVa9NDGm + hyZtQk89m2bFDdAiUBZqPfjfO7OzC4tgpC7p0XFn980Hb9+sSX3VMs19laEVwMXUtwHfyH10tjvD + kvTXLtGeBVy46CH0JhEYwigIYXdHfOMtpSJ+JiOeIKkVkmEHVsq0YcC2kWqY99Yw58PAJMppAn3V + yk9CnVtDXQwDFTqyidNLDxrkwhrkcjCQrRb1pE1DXY5PG1Rn5pdoB/0/WwGoxi9Fke7lgPkOSma2 + 6MdgLEESIuex1L5OdvyJhBPknCdhmgEPOR9Rzh6CcMqzyJ+qEYBcaMKA02qye4JFI48WKUHdgtZB + exd5G1k5ZltO8qlHRGe3TVtAq91qyB4ZNF71v90FPHPuXNrIFWUApS3WXyXPu6/bzuC7hgrAzIqC + +eG+99x2ng/D38iJYa3z8mgupdw0vPvFQRH4aXasXxiwiW6sp+Fv4DesRl3hN9+N5ONDXd7GBjYl + Vk8JLjT8J8/XfpnjsKvmnJfdaiLC9PBWbnFGp7l4hZe7aRZkUa4SKEqypIzjyU3tUqUZAQcJlzR2 + PcnnSrXyNppBm6pWqA12WZRKlXZj25i7guNLxVop8pWcxP6aDWIO1Oyb0y8AAAD//wMAwx2Ze08X + AAA= + headers: + Atl-Request-Id: + - 9b9de90e-9770-4c4f-b479-af77d21902e3 + Atl-Traceid: + - 9b9de90e97704c4fb479af77d21902e3 + Cache-Control: + - no-cache, no-store, no-transform + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:31 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=389,atl-edge-internal;dur=13,atl-edge-upstream;dur=377,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + Warning: + - 'The issue create meta endpoint has been deprecated. (Deprecation start date: + June 03, 2024)' + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - d953c94eb48f98edbaca677689484a6b + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: '{"fields": {"project": {"key": "NTEST"}, "issuetype": {"name": "Task"}, + "summary": "Zap1: Cookie Without Secure Flag", "description": "\n\n\n\n\n\n*Title*: + [Zap1: Cookie Without Secure Flag|http://localhost:8080/finding/234]\n\n*Defect + Dojo link:* http://localhost:8080/finding/234 (234)\n\n*Severity:* Low\n\n\n*Due + Date:* Feb. 27, 2025\n\n\n\n*CWE:* [CWE-614|https://cwe.mitre.org/data/definitions/614.html]\n\n\n\n*CVE:* + Unknown\n\n\n\n\n*Product/Engagement/Test:* [Security How-to|http://localhost:8080/product/2] + / [1st Quarter Engagement|http://localhost:8080/engagement/1] / [ZAP Scan|http://localhost:8080/test/91]\n\n\n\n\n\n\n\n\n*Systems/Endpoints*:\n\n* + https://mainsite.com/dashboard\n* https://mainsite.com\n\n\n\n\n\n\n\n*Description*:\nA + cookie has been set without the secure flag, which means that the cookie can\nbe + accessed via unencrypted connections.\n\n\n\n\n*Mitigation*:\nWhenever a cookie + contains sensitive information or is a session token, then\nit should always + be passed using an encrypted channel. Ensure that the secure\nflag is set for + cookies containing such sensitive information.\n\n\n\n\n\n\n\n\n\n*References*:\nhttp://www.owasp.org/index.php/Testing_for_cookies_attributes_(OWASP-SM-002)\n\n\n\n\n*Reporter:* + [(admin) ()|mailto:]\n", "priority": {"name": "Low"}}}' + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Length: + - '1303' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://defectdojo.atlassian.net/rest/api/2/issue + response: + body: + string: '{"id":"13279","key":"NTEST-1562","self":"https://defectdojo.atlassian.net/rest/api/2/issue/13279"}' + headers: + Atl-Request-Id: + - 3d4a9e1f-0074-4a46-8329-d9ea93ef3d67 + Atl-Traceid: + - 3d4a9e1f00744a468329d9ea93ef3d67 + Cache-Control: + - no-cache, no-store, no-transform + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:31 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=725,atl-edge-internal;dur=26,atl-edge-upstream;dur=688,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - 3f283b48e63a21e8bb12cc5e52dc6df1 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://defectdojo.atlassian.net/rest/api/2/issue/NTEST-1562 + response: + body: + string: !!binary | + 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 + tD2gtX14eHgEAAD//wMAN0rIpNIcAAA= + headers: + Atl-Request-Id: + - 5a8a3ef7-b56c-4709-be85-0f54d7ee00b7 + Atl-Traceid: + - 5a8a3ef7b56c4709be850f54d7ee00b7 + Cache-Control: + - no-cache, no-store, no-transform + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:32 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=277,atl-edge-internal;dur=14,atl-edge-upstream;dur=261,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - 33862545e0dfec070ea29f24df4d9983 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://defectdojo.atlassian.net/rest/api/2/issue/13279 + response: + body: + string: !!binary | + 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 + /fPD0bPT5oDSdrVa/QYAAP//AwDMRS2O0hwAAA== + headers: + Atl-Request-Id: + - 2f37d096-2d63-46e6-b74d-d0d4211e1943 + Atl-Traceid: + - 2f37d0962d6346e6b74dd0d4211e1943 + Cache-Control: + - no-cache, no-store, no-transform + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:32 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=351,atl-edge-internal;dur=15,atl-edge-upstream;dur=336,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - d03ca4312096eb9c66ff0d51ab858756 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://defectdojo.atlassian.net/rest/api/2/serverInfo + response: + body: + string: !!binary | + H4sIAAAAAAAAA5yQT0/DMAzFv0uubJ37b+lyQwVpIDSQ2l1ACKWNKwJpUjXppGnadycRA3YEfLLs + 3/N78oE03OJ2VISRV+cGyxYLgR22Tpg3E3GnuLWS60ijIzMipB0U3/+Dr3DcyRYF2vc1qqFE7XD8 + 65HS6E5NqFv8nXKHo5VGezgGiCOIYF5tLh+q9X39s91MfeM7wp4CNIMZPHtPHJTZ9z5lvR+CW6nM + JLyomaQSnxLCvCChyWl4xV0AE0iyeQzzZFXHOcsKFqcRAFyAh73e+j/gWMv+nE2hjguWZyz1bEy/ + 2ba/0Z3xIApoM0qXgEWTJ1lOi4wv01We+Gq6FRWcguiWIeCXgVPB4VaOPLwQOz4pd2daHsYHok4d + Qf2yrcjxPNij0WFz7drFti7J8QMAAP//AwCkdd+oJAIAAA== + headers: + Atl-Request-Id: + - 5d1ac2f1-aec4-439f-a030-9e1cd75314d2 + Atl-Traceid: + - 5d1ac2f1aec4439fa0309e1cd75314d2 + Cache-Control: + - no-cache, no-store, no-transform + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:33 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=169,atl-edge-internal;dur=13,atl-edge-upstream;dur=156,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - 1d82593d270dd251bff381102625b2a4 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://defectdojo.atlassian.net/rest/api/2/issue/createmeta?projectKeys=NTEST&issuetypeNames=Task&expand=projects.issuetypes.fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA+xW30/bMBD+V6o87KkibakYqlShaWwS2oSmUXiZ0OQl19Xg2Jl9adOh/u87J07s + QmGBbeyFPsWX+/Gd77uvuYmgzJlMo0mUa3UFCZqo7x8nX268AzemAFznYF0MiDnZFoi5mcRxCnMK + SNWV2mMomDGcyT0JGGswGLOcx6PYZY2HA/pRCm6TNodrWNPpdPbubEYnyTKg47nkiJTAFmRLhkyf + a0GobqLxYTk+fGT9QvIlaMPE1zpXvOSwim1DLTT3YjgYD15TzdG4HI3/bZUjw3/C1GRMCCo4PCiH + B89RsGwq7o/K/dFzVMwg5UUWbfohjyy/nsCkNkPFpVHIJXtIwSSa58iVJOubXtVrv5dyg1wm2Ms5 + JNBT895K6es9G50oScx6JIoHrsEDbC9if3i4dREtyWfMXNOpkKiZNIIhpKdbb0zxDe3TZM6EgX60 + 4KCZThbrj7AEAj3o+x2dcxCpXRf3QKtiiixjem0fNfwouAZyRF1QJpMsIGP2jcVK4QY1l99tzbVB + yKzFRW9avGfO0uxs40HAmDmGOSsEXjBRQAtY5QTYTsMOnOaN0WVIg07IvHcAzhs9vBNr681qzxph + GNsJ4yXJjRBqBWnl9MLTB9hYB55QX1XsDnpu7LRzpkHi9qhdht2zFlxW5ZpZuwR+0J9qQzNk977j + hFsWJirLlaTIald+j41pzSzXOaGiEB8fYg2SerxvvbHBHPh1xc3S1FYCW09DppYQ7WCr7WyLXV1a + u7v8YQrfyHFgbToJPbu20ozA/Wd0koHGNySGMwXMaJ0cNdrzI4DdvdQnScCfffS4aKtmH2qxVXNc + EdPtBfAsF5x03o/05Rvpbxb8f99IlWJqyJVG0J32ojDkGCxFG+y34nNjanjX+hBxClRWnwQgPOEP + horTmdQsA5ke0Z8UQonTpuCrSs+Jv9NdG1g3tGMBN/YjprygS6zNXSTstjrbEdSS1N5MmNNfznte + 9pyzl+fQdQfye7Wj71T6YX3ONVea460Ps/s6a723lM/ZfgEAAP//vFhNb4JAEP0rxoM3oVa9NDGm + hyZtQk89m2bFDdAiUBZqPfjfO7OzC4tgpC7p0XFn980Hb9+sSX3VMs19laEVwMXUtwHfyH10tjvD + kvTXLtGeBVy46CH0JhEYwigIYXdHfOMtpSJ+JiOeIKkVkmEHVsq0YcC2kWqY99Yw58PAJMppAn3V + yk9CnVtDXQwDFTqyidNLDxrkwhrkcjCQrRb1pE1DXY5PG1Rn5pdoB/0/WwGoxi9Fke7lgPkOSma2 + 6MdgLEESIuex1L5OdvyJhBPknCdhmgEPOR9Rzh6CcMqzyJ+qEYBcaMKA02qye4JFI48WKUHdgtZB + exd5G1k5ZltO8qlHRGe3TVtAq91qyB4ZNF71v90FPHPuXNrIFWUApS3WXyXPu6/bzuC7hgrAzIqC + +eG+99x2ng/D38iJYa3z8mgupdw0vPvFQRH4aXasXxiwiW6sp+Fv4DesRl3hN9+N5ONDXd7GBjYl + Vk8JLjT8J8/XfpnjsKvmnJfdaiLC9PBWbnFGp7l4hZe7aRZkUa4SKEqypIzjyU3tUqUZAQcJlzR2 + PcnnSrXyNppBm6pWqA12WZRKlXZj25i7guNLxVop8pWcxP6aDWIO1Oyb0y8AAAD//wMAwx2Ze08X + AAA= + headers: + Atl-Request-Id: + - 300b0821-0ed1-4b0e-8e47-338733f47ba6 + Atl-Traceid: + - 300b08210ed14b0e8e47338733f47ba6 + Cache-Control: + - no-cache, no-store, no-transform + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:33 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=229,atl-edge-internal;dur=14,atl-edge-upstream;dur=216,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + Warning: + - 'The issue create meta endpoint has been deprecated. (Deprecation start date: + June 03, 2024)' + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - 34006bd9622a242c30a95a5a6475720c + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: '{"fields": {"project": {"key": "NTEST"}, "issuetype": {"name": "Task"}, + "summary": "Zap2: Cookie Without Secure Flag", "description": "\n\n\n\n\n\n*Title*: + [Zap2: Cookie Without Secure Flag|http://localhost:8080/finding/235]\n\n*Defect + Dojo link:* http://localhost:8080/finding/235 (235)\n\n*Severity:* Low\n\n\n*Due + Date:* Feb. 27, 2025\n\n\n\n*CWE:* [CWE-614|https://cwe.mitre.org/data/definitions/614.html]\n\n\n\n*CVE:* + Unknown\n\n\n\n\n*Product/Engagement/Test:* [Security How-to|http://localhost:8080/product/2] + / [1st Quarter Engagement|http://localhost:8080/engagement/1] / [ZAP Scan|http://localhost:8080/test/91]\n\n\n\n\n\n\n\n\n*Systems/Endpoints*:\n\n* + https://mainsite.com/dashboard\n* https://mainsite.com\n\n\n\n\n\n\n\n*Description*:\nA + cookie has been set without the secure flag, which means that the cookie can\nbe + accessed via unencrypted connections.\n\n\n\n\n*Mitigation*:\nWhenever a cookie + contains sensitive information or is a session token, then\nit should always + be passed using an encrypted channel. Ensure that the secure\nflag is set for + cookies containing such sensitive information.\n\n\n\n\n\n\n\n\n\n*References*:\nhttp://www.owasp.org/index.php/Testing_for_cookies_attributes_(OWASP-SM-002)\n\n\n\n\n*Reporter:* + [(admin) ()|mailto:]\n", "priority": {"name": "Low"}}}' + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Length: + - '1303' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://defectdojo.atlassian.net/rest/api/2/issue + response: + body: + string: '{"id":"13280","key":"NTEST-1563","self":"https://defectdojo.atlassian.net/rest/api/2/issue/13280"}' + headers: + Atl-Request-Id: + - a5f2d568-bc63-4a1a-8242-612e6b546398 + Atl-Traceid: + - a5f2d568bc634a1a8242612e6b546398 + Cache-Control: + - no-cache, no-store, no-transform + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:34 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=704,atl-edge-internal;dur=14,atl-edge-upstream;dur=691,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - 5d8a8d2e7551db5c5c162f4a0bee0eb8 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://defectdojo.atlassian.net/rest/api/2/issue/NTEST-1563 + response: + body: + string: !!binary | + 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 + AaXtw8PDIwAAAP//AwBUMoCu0hwAAA== + headers: + Atl-Request-Id: + - 06053acd-93ec-4372-aaff-5e9fdb8c5da5 + Atl-Traceid: + - 06053acd93ec4372aaff5e9fdb8c5da5 + Cache-Control: + - no-cache, no-store, no-transform + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:34 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=282,atl-edge-internal;dur=15,atl-edge-upstream;dur=267,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - d03e01dec761188b14c94beb46f97414 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json,*/*;q=0.9 + Accept-Encoding: + - gzip, deflate + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.3 + method: GET + uri: https://defectdojo.atlassian.net/rest/api/2/issue/13280 + response: + body: + string: !!binary | + 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 + 9oDW9uHh4TcAAAD//wMAcEoEh9IcAAA= + headers: + Atl-Request-Id: + - 420a5388-9a6b-48bf-9c5b-2503e8098751 + Atl-Traceid: + - 420a53889a6b48bf9c5b2503e8098751 + Cache-Control: + - no-cache, no-store, no-transform + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=UTF-8 + Date: + - Wed, 30 Oct 2024 17:54:34 GMT + Nel: + - '{"failure_fraction": 0.001, "include_subdomains": true, "max_age": 600, "report_to": + "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": + "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=253,atl-edge-internal;dur=11,atl-edge-upstream;dur=241,atl-edge-pop;desc="aws-us-east-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Timing-Allow-Origin: + - '*' + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding + X-Aaccountid: + - 5fa43d1b8405b10077912260 + X-Arequestid: + - ca2e36ac3a932db5a94e7f87b41fcb73 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: '{"description": null, "user": null, "url_ui": "http://localhost:8080/test/91", + "url_api": "http://localhost:8080/api/v2/tests/91/", "product_type": {"name": + "ebooks", "id": 2, "url_ui": "http://localhost:8080/product/type/2", "url_api": + "http://localhost:8080/api/v2/product_types/2/"}, "product": {"name": "Security + How-to", "id": 2, "url_ui": "http://localhost:8080/product/2", "url_api": "http://localhost:8080/api/v2/products/2/"}, + "engagement": {"name": "1st Quarter Engagement", "id": 1, "url_ui": "http://localhost:8080/engagement/1", + "url_api": "http://localhost:8080/api/v2/engagements/1/"}, "test": {"title": + null, "id": 91, "url_ui": "http://localhost:8080/test/91", "url_api": "http://localhost:8080/api/v2/tests/91/"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Auth: + - Token xxx + Connection: + - keep-alive + Content-Length: + - '731' + Content-Type: + - application/json + User-Agent: + - DefectDojo-2.40.0-dev + X-DefectDojo-Event: + - test_added + X-DefectDojo-Instance: + - http://localhost:8080 + method: POST + uri: http://webhook.endpoint:8080/post + response: + body: + string: "{\n \"args\": {},\n \"headers\": {\n \"Accept\": [\n \"application/json\"\n + \ ],\n \"Accept-Encoding\": [\n \"gzip, deflate\"\n ],\n \"Auth\": + [\n \"Token xxx\"\n ],\n \"Connection\": [\n \"keep-alive\"\n + \ ],\n \"Content-Length\": [\n \"731\"\n ],\n \"Content-Type\": + [\n \"application/json\"\n ],\n \"Host\": [\n \"webhook.endpoint:8080\"\n + \ ],\n \"User-Agent\": [\n \"DefectDojo-2.40.0-dev\"\n ],\n \"X-Defectdojo-Event\": + [\n \"test_added\"\n ],\n \"X-Defectdojo-Instance\": [\n \"http://localhost:8080\"\n + \ ]\n },\n \"method\": \"POST\",\n \"origin\": \"172.18.0.2:44004\",\n + \ \"url\": \"http://webhook.endpoint:8080/post\",\n \"data\": \"{\\\"description\\\": + null, \\\"user\\\": null, \\\"url_ui\\\": \\\"http://localhost:8080/test/91\\\", + \\\"url_api\\\": \\\"http://localhost:8080/api/v2/tests/91/\\\", \\\"product_type\\\": + {\\\"name\\\": \\\"ebooks\\\", \\\"id\\\": 2, \\\"url_ui\\\": \\\"http://localhost:8080/product/type/2\\\", + \\\"url_api\\\": \\\"http://localhost:8080/api/v2/product_types/2/\\\"}, \\\"product\\\": + {\\\"name\\\": \\\"Security How-to\\\", \\\"id\\\": 2, \\\"url_ui\\\": \\\"http://localhost:8080/product/2\\\", + \\\"url_api\\\": \\\"http://localhost:8080/api/v2/products/2/\\\"}, \\\"engagement\\\": + {\\\"name\\\": \\\"1st Quarter Engagement\\\", \\\"id\\\": 1, \\\"url_ui\\\": + \\\"http://localhost:8080/engagement/1\\\", \\\"url_api\\\": \\\"http://localhost:8080/api/v2/engagements/1/\\\"}, + \\\"test\\\": {\\\"title\\\": null, \\\"id\\\": 91, \\\"url_ui\\\": \\\"http://localhost:8080/test/91\\\", + \\\"url_api\\\": \\\"http://localhost:8080/api/v2/tests/91/\\\"}}\",\n \"files\": + {},\n \"form\": {},\n \"json\": {\n \"description\": null,\n \"engagement\": + {\n \"id\": 1,\n \"name\": \"1st Quarter Engagement\",\n \"url_api\": + \"http://localhost:8080/api/v2/engagements/1/\",\n \"url_ui\": \"http://localhost:8080/engagement/1\"\n + \ },\n \"product\": {\n \"id\": 2,\n \"name\": \"Security How-to\",\n + \ \"url_api\": \"http://localhost:8080/api/v2/products/2/\",\n \"url_ui\": + \"http://localhost:8080/product/2\"\n },\n \"product_type\": {\n \"id\": + 2,\n \"name\": \"ebooks\",\n \"url_api\": \"http://localhost:8080/api/v2/product_types/2/\",\n + \ \"url_ui\": \"http://localhost:8080/product/type/2\"\n },\n \"test\": + {\n \"id\": 91,\n \"title\": null,\n \"url_api\": \"http://localhost:8080/api/v2/tests/91/\",\n + \ \"url_ui\": \"http://localhost:8080/test/91\"\n },\n \"url_api\": + \"http://localhost:8080/api/v2/tests/91/\",\n \"url_ui\": \"http://localhost:8080/test/91\",\n + \ \"user\": null\n }\n}\n" + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Origin: + - '*' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 30 Oct 2024 17:54:34 GMT + Transfer-Encoding: + - chunked + status: + code: 200 + message: OK +version: 1 From 1b68cbe75ff5aaed23af2f5ca833e2e57b61efa5 Mon Sep 17 00:00:00 2001 From: Paul Osinski <42211303+paulOsinski@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:19:16 -0500 Subject: [PATCH 70/74] correct broken documentation links (#11178) * correct broken documentation links * Update docs/content/en/getting_started/architecture.md Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update installation.md with new AWS launch guide --------- Co-authored-by: Paul Osinski Co-authored-by: Jay Paz Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> --- docs/content/en/getting_started/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/en/getting_started/installation.md b/docs/content/en/getting_started/installation.md index a127f36e492..8f6affa702e 100644 --- a/docs/content/en/getting_started/installation.md +++ b/docs/content/en/getting_started/installation.md @@ -14,11 +14,11 @@ See instructions in [DOCKER.md]( Date: Mon, 4 Nov 2024 10:19:27 -0600 Subject: [PATCH 71/74] Burp Enterprise: Add hash code fields (#11179) * Update hash code fields * Update sha --- dojo/settings/.settings.dist.py.sha256sum | 2 +- dojo/settings/settings.dist.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index 9206f7bf5ed..2347c09dca8 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -4b0c6ee05222e622f74d80c8e93504aba986ad0b187aab305ff7ecef89080f11 +39cdd5dfe53499bfe201d3e5a0f55b20514272235e86db7d5238f2663b79f946 diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 91f72e40c19..783d42fcc3b 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1201,6 +1201,7 @@ def saml2_attrib_map_format(dict): "Anchore Grype": ["title", "severity", "component_name", "component_version"], "Aqua Scan": ["severity", "vulnerability_ids", "component_name", "component_version"], "Bandit Scan": ["file_path", "line", "vuln_id_from_tool"], + "Burp Enterprise Scan": ["title", "severity", "cwe"], "CargoAudit Scan": ["vulnerability_ids", "severity", "component_name", "component_version", "vuln_id_from_tool"], "Checkmarx Scan": ["cwe", "severity", "file_path"], "Checkmarx OSA": ["vulnerability_ids", "component_name"], @@ -1403,6 +1404,7 @@ def saml2_attrib_map_format(dict): "AWS Security Finding Format (ASFF) Scan": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, "Burp REST API": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, "Bandit Scan": DEDUPE_ALGO_HASH_CODE, + "Burp Enterprise Scan": DEDUPE_ALGO_HASH_CODE, "CargoAudit Scan": DEDUPE_ALGO_HASH_CODE, "Checkmarx Scan detailed": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, "Checkmarx Scan": DEDUPE_ALGO_HASH_CODE, From 7c13110c1de17b46dc5496b9d7ac72e775179a3b Mon Sep 17 00:00:00 2001 From: Ross E Esposito Date: Mon, 4 Nov 2024 10:37:03 -0600 Subject: [PATCH 72/74] Update .settings.dist.py.sha256sum --- dojo/settings/.settings.dist.py.sha256sum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index 11afd6a9628..2347c09dca8 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -fcc15cb97df6ff9ef6752a1fe498502126174285e37d67640a6cc7a83314d951 \ No newline at end of file +39cdd5dfe53499bfe201d3e5a0f55b20514272235e86db7d5238f2663b79f946 From 927305031f94c06e9275244dcb79c09c06ffcb0d Mon Sep 17 00:00:00 2001 From: Ross Esposito Date: Mon, 4 Nov 2024 10:52:17 -0600 Subject: [PATCH 73/74] Fixing sha again --- dojo/settings/.settings.dist.py.sha256sum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index 2347c09dca8..259f13a4c69 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -39cdd5dfe53499bfe201d3e5a0f55b20514272235e86db7d5238f2663b79f946 +6b9365d002880ae64ab54da905ede076db5a8661960f8f1e2793b7f4d25ff7e8 From 0668466a8db9abe5aff85322ee3b7680bbf5b2af Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 4 Nov 2024 17:29:33 +0000 Subject: [PATCH 74/74] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/package.json b/components/package.json index 74a5293b38a..14dc5baf9da 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.40.0-dev", + "version": "2.40.0", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index 0dc36e95a1e..8c5bb4603e6 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.40.0-dev" +__version__ = "2.40.0" __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 af7a77d7b50..3744d4461a4 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.40.0-dev" +appVersion: "2.40.0" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.158-dev +version: 1.6.158 icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap