diff --git a/.github/workflows/k8s-tests.yml b/.github/workflows/k8s-tests.yml
index d2da08eb7f..60f8bc3c38 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 }}
diff --git a/Dockerfile.integration-tests-debian b/Dockerfile.integration-tests-debian
index 3a01815f82..ae890a24c1 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 \
diff --git a/Dockerfile.nginx-alpine b/Dockerfile.nginx-alpine
index 0528e63047..b1bd293b09 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 b07ce5407d..f818e54c7f 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/
diff --git a/components/package.json b/components/package.json
index 805b16ff7c..14dc5baf9d 100644
--- a/components/package.json
+++ b/components/package.json
@@ -1,6 +1,6 @@
{
"name": "defectdojo",
- "version": "2.39.4",
+ "version": "2.40.0",
"license" : "BSD-3-Clause",
"private": true,
"dependencies": {
@@ -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",
@@ -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 7bb1936579..7f7ddd04d7 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"
@@ -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"
diff --git a/docs/assets/icons/logo.svg b/docs/assets/icons/logo.svg
index 71a24baac7..75983a5271 100644
--- a/docs/assets/icons/logo.svg
+++ b/docs/assets/icons/logo.svg
@@ -1,244 +1,15 @@
-
-
+
\ No newline at end of file
diff --git a/docs/config.dev.toml b/docs/config.dev.toml
index 65fff4564b..de3d1b24c3 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 29c4e0a6ad..22f2f7748a 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]
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 c87846cb62..5652f0dbc5 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 %}}
diff --git a/docs/content/en/getting_started/installation.md b/docs/content/en/getting_started/installation.md
index a127f36e49..8f6affa702 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](
@@ -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 64fd7746ec..36e31586a5 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 2d90a6a681..dea3cd27f2 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 1171f51383..e5db413929 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 27a40e6cab..ea1a6bffa3 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 8614a80e0a..bf6d71dc6f 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/docs/content/en/integrations/parsers/file/aws_inspector2.md b/docs/content/en/integrations/parsers/file/aws_inspector2.md
new file mode 100644
index 0000000000..d7507b6168
--- /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/docs/content/en/integrations/parsers/file/ptart.md b/docs/content/en/integrations/parsers/file/ptart.md
new file mode 100644
index 0000000000..5ce5696749
--- /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/__init__.py b/dojo/__init__.py
index 9572cd8932..8c5bb4603e 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"
__url__ = "https://github.com/DefectDojo/django-DefectDojo"
__docs__ = "https://documentation.defectdojo.com"
diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py
index 56a1826506..2680e8f1ad 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
@@ -2258,6 +2257,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
diff --git a/dojo/apps.py b/dojo/apps.py
index fd3a06575f..4d4d07af50 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/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 0000000000..beec72caff
--- /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/engagement/views.py b/dojo/engagement/views.py
index b813a9c275..9cfab60889 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
@@ -428,7 +427,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)
@@ -711,9 +710,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
@@ -770,7 +769,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
@@ -795,7 +794,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
@@ -812,9 +811,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
@@ -1047,8 +1046,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
@@ -1063,8 +1062,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
@@ -1556,8 +1555,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 ea5578ee46..0c0d78d6cc 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 546dae9376..814d88888e 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 cb1e670054..3577a9ff2a 100644
--- a/dojo/forms.py
+++ b/dojo/forms.py
@@ -2399,10 +2399,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)
@@ -3079,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 5fe1ca35c1..dc1f865b12 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/home/views.py b/dojo/home/views.py
index 3c485eb1e9..3f5c5870d0 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 1645456862..7d54782a67 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 ebd97fc37f..c9a77fbb95 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 28a2de1e30..95254ef59b 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 7f1c3bd15b..9debf4aaba 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 6686584da3..625e3cb807 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 2431975856..f458f2a4f3 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/jira_link/helper.py b/dojo/jira_link/helper.py
index 860f4f01d1..f1ce769fb2 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 78ea8cef6c..a49ae04050 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 c44fbe68f1..164bc8e970 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 191c454b6e..9a4b9f6bb0 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
@@ -108,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,
@@ -257,7 +260,7 @@ class _MetricsPeriodEntry(NamedTuple):
"""
datetime_name: str
- db_method: Union[TruncWeek, TruncMonth]
+ db_method: TruncWeek | TruncMonth
class MetricsPeriod(_MetricsPeriodEntry, Enum):
@@ -346,7 +349,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 +396,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/metrics/views.py b/dojo/metrics/views.py
index 631130b58d..85d9654c6c 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/middleware.py b/dojo/middleware.py
index 29eff445b3..9fcb8a51db 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/dojo/models.py b/dojo/models.py
index a5bbe7c01f..dba8f45c44 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
@@ -359,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 "
@@ -1196,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,
@@ -1544,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)
@@ -2114,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)
@@ -2738,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):
@@ -3409,7 +3415,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)
@@ -3912,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/notifications/helper.py b/dojo/notifications/helper.py
index ce3f52bf1a..46d0339dd3 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/remote_user.py b/dojo/remote_user.py
index 764af4e548..a60fe52c89 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 aacf436933..f258db9db2 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
@@ -85,16 +84,20 @@ 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)
- 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),
@@ -187,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)
@@ -261,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)
@@ -649,8 +656,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 +877,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 +892,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 +903,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
@@ -953,6 +956,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
@@ -1004,6 +1008,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
@@ -1021,8 +1026,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 +1042,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/reports/widgets.py b/dojo/reports/widgets.py
index 828adada0a..7439a4bb8f 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/risk_acceptance/api.py b/dojo/risk_acceptance/api.py
index 4fc89a32fe..2fdaadf0af 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/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum
index def3909d2c..259f13a4c6 100644
--- a/dojo/settings/.settings.dist.py.sha256sum
+++ b/dojo/settings/.settings.dist.py.sha256sum
@@ -1 +1 @@
-002b28325f11793c5aa9f09326c2d5cc66de518cce51b2cb4cb681a920b89909
+6b9365d002880ae64ab54da905ede076db5a8661960f8f1e2793b7f4d25ff7e8
diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py
index 846639f07a..9920533272 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
@@ -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"],
@@ -1283,6 +1284,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 +1352,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)
@@ -1401,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,
@@ -1510,10 +1514,12 @@ 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,
"KrakenD Audit Scan": DEDUPE_ALGO_HASH_CODE,
+ "PTART Report": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
}
# Override the hardcoded settings here via the env var
@@ -1736,6 +1742,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
diff --git a/dojo/system_settings/views.py b/dojo/system_settings/views.py
index 4c952d57a0..584fa547d3 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/templates/notifications/webhooks/subtemplates/base.tpl b/dojo/templates/notifications/webhooks/subtemplates/base.tpl
index 44946cd8dd..3b6e30da98 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/dojo/test/views.py b/dojo/test/views.py
index 96d3a58c1e..76b0bcd2aa 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 6d10485e4d..b34931d0f8 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 782c047443..e07433c294 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 9252bdcb53..3fba10e455 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/aws_inspector2/__init__.py b/dojo/tools/aws_inspector2/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/dojo/tools/aws_inspector2/parser.py b/dojo/tools/aws_inspector2/parser.py
new file mode 100644
index 0000000000..863c9ebc18
--- /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/dojo/tools/blackduck/importer.py b/dojo/tools/blackduck/importer.py
index 8317537170..51caa18076 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 4d381aae05..0ada8cca26 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 a48023e5d6..f8896c0b27 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 2fa4dd3c29..7516cfe211 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 5eb30227b5..31645ab7da 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/mend/parser.py b/dojo/tools/mend/parser.py
index 75ed871a6a..6bcc96f750 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/dojo/tools/ptart/__init__.py b/dojo/tools/ptart/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/dojo/tools/ptart/assessment_parser.py b/dojo/tools/ptart/assessment_parser.py
new file mode 100644
index 0000000000..02387a7d65
--- /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 0000000000..c52ebf4fb4
--- /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 0000000000..f538a81f3c
--- /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 0000000000..812a458344
--- /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/dojo/tools/sarif/parser.py b/dojo/tools/sarif/parser.py
index e6c6838884..ae9736e9a0 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/csv_format.py b/dojo/tools/tenable/csv_format.py
index 2c2e013446..3b38bd5a7a 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/dojo/tools/tenable/xml_format.py b/dojo/tools/tenable/xml_format.py
index 7094e82d62..438ce2220e 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/trivy_operator/checks_handler.py b/dojo/tools/trivy_operator/checks_handler.py
index e6a1ccd8bb..c42eef0fa8 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 138cee1762..8be42e8e31 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 c5e767a1bc..a00c894a03 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 13be3e55a4..a5a26e1288 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/dojo/tools/veracode/json_parser.py b/dojo/tools/veracode/json_parser.py
index 6584d21382..55ea07602d 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 325bd36428..c23d002cb8 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 8bbd531210..c57695c09d 100644
--- a/dojo/utils.py
+++ b/dojo/utils.py
@@ -8,9 +8,9 @@
import pathlib
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
@@ -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
@@ -2222,7 +2290,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}"
@@ -2587,7 +2655,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/helm/defectdojo/Chart.lock b/helm/defectdojo/Chart.lock
index 49d15928ea..611d1100a4 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 5337b2b705..3744d4461a 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"
description: A Helm chart for Kubernetes to install DefectDojo
name: defectdojo
-version: 1.6.157
+version: 1.6.158
icon: https://www.defectdojo.org/img/favicon.ico
maintainers:
- name: madchap
@@ -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
diff --git a/helm/defectdojo/templates/configmap.yaml b/helm/defectdojo/templates/configmap.yaml
index b5248a0a9b..5ae741f0ab 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 }}
diff --git a/helm/defectdojo/values.yaml b/helm/defectdojo/values.yaml
index 67c41eeab3..b2d0422bc2 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: ""
diff --git a/requirements-lint.txt b/requirements-lint.txt
index e2fd91d90a..8bf2f34823 100644
--- a/requirements-lint.txt
+++ b/requirements-lint.txt
@@ -1 +1 @@
-ruff==0.6.8
\ No newline at end of file
+ruff==0.7.1
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index a2ffb14422..949f2e5793 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
@@ -21,56 +21,56 @@ 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.9
+Django==5.1.2
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
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
+cryptography==43.0.3
python-dateutil==2.9.0.post0
pytz==2024.2
-redis==5.1.0
+redis==5.2.0
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
+uWSGI==2.0.28
vobject==0.9.8
whitenoise==5.2.0
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==5.0.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
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
-cvss==3.2
+cvss==3.3
django-fieldsignals==0.7.0
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
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.53 # Required for Celery Broker AWS (SQS) support
netaddr==1.3.0
-vulners==2.2.1
+vulners==2.2.3
fontawesomefree==6.6.0
PyYAML==6.0.2
diff --git a/ruff.toml b/ruff.toml
index 376a6d3cc7..cb7e923125 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
@@ -34,11 +37,11 @@ select = [
"W",
"C90",
"I",
- "D2", "D3",
+ "D2", "D3", "D403",
"UP",
"YTT",
"ASYNC",
- "S2", "S5", "S7",
+ "S2", "S5", "S7", "S101", "S112", "S311",
"FBT001", "FBT003",
"A003", "A004", "A006",
"COM",
@@ -72,7 +75,6 @@ select = [
"TRY003",
"TRY004",
"TRY2",
- "TRY302",
"FLY",
"NPY",
"FAST",
diff --git a/tests/Import_scanner_test.py b/tests/Import_scanner_test.py
index b1597e9583..3006393aec 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 e64527cec9..d71067ce68 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/dojo_test_case.py b/unittests/dojo_test_case.py
index 39d65e113b..d8995e5d0d 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/scans/aws_inspector2/aws_inspector2_many_vul.json b/unittests/scans/aws_inspector2/aws_inspector2_many_vul.json
new file mode 100644
index 0000000000..c3083037c6
--- /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 0000000000..dac01fadd2
--- /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 0000000000..1ed172b30b
--- /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 0000000000..9e26dfeeb6
--- /dev/null
+++ b/unittests/scans/aws_inspector2/empty_with_error.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/unittests/scans/mend/mend_sca_vuln.json b/unittests/scans/mend/mend_sca_vuln.json
new file mode 100644
index 0000000000..6af95cb315
--- /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/scans/ptart/empty_with_error.json b/unittests/scans/ptart/empty_with_error.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /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 0000000000..1e6afebcff
--- /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 0000000000..67930bc339
--- /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/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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": [
+ {
+ "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 0000000000..ad0f0dca0a
--- /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 0000000000..1ad9d01d1f
--- /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 0000000000..bfdf77d03a
--- /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/scans/trivy_operator/findings_in_list.json b/unittests/scans/trivy_operator/findings_in_list.json
new file mode 100644
index 0000000000..7a79600eb8
--- /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/test_apiv2_notifications.py b/unittests/test_apiv2_notifications.py
index d24a05f596..09d57dfd9f 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/test_bulk_risk_acceptance_api.py b/unittests/test_bulk_risk_acceptance_api.py
index bdc87451d0..05bbe10e7a 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 81d9000e40..35e3eabbde 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 7ff00889c0..8d3432864d 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 a75473664b..1c7f5ef08d 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 03cea9b0b0..b0f7da906b 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_jira_import_and_pushing_api.py b/unittests/test_jira_import_and_pushing_api.py
index a086fb579b..eeba03f974 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 6bd54ff9e8..68dd1f43fe 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,11 +75,11 @@ 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
- with self.assertNumQueries(27):
+ with self.assertNumQueries(28):
product_types = []
finding_queries = utils.finding_queries(
product_types,
@@ -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_notifications.py b/unittests/test_notifications.py
index 7f5a2b76a4..cccdb2e3d6 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}",
diff --git a/unittests/test_product_grading.py b/unittests/test_product_grading.py
new file mode 100644
index 0000000000..bc1aa557d3
--- /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/test_rest_framework.py b/unittests/test_rest_framework.py
index 4021efc121..8b2c75e762 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):
diff --git a/unittests/test_risk_acceptance.py b/unittests/test_risk_acceptance.py
index 97afb3e1f7..9e7904f471 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_tags.py b/unittests/test_tags.py
index a1e1aa20cc..3f93129fa8 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)
diff --git a/unittests/test_utils_deduplication_reopen.py b/unittests/test_utils_deduplication_reopen.py
index 1876deefe3..a5f8fcf54d 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_appcheck_web_application_scanner_parser.py b/unittests/tools/test_appcheck_web_application_scanner_parser.py
index 5c3e7dc96b..9360eb9209 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?'\"\""),
diff --git a/unittests/tools/test_arachni_parser.py b/unittests/tools/test_arachni_parser.py
index 337200796e..266d45dc05 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_aws_inspector2_parser.py b/unittests/tools/test_aws_inspector2_parser.py
new file mode 100644
index 0000000000..f023bec88a
--- /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),
+ )
diff --git a/unittests/tools/test_bugcrowd_parser.py b/unittests/tools/test_bugcrowd_parser.py
index 5e66c9c6c7..87a3083ffb 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 8f39cc6f70..620e6adfc6 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_github_vulnerability_parser.py b/unittests/tools/test_github_vulnerability_parser.py
index c0c9a0350e..00321647bf 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_mend_parser.py b/unittests/tools/test_mend_parser.py
index 393dd4097c..1cd8cc11dd 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)
diff --git a/unittests/tools/test_ptart_parser.py b/unittests/tools/test_ptart_parser.py
new file mode 100644
index 0000000000..83be6417b3
--- /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")
diff --git a/unittests/tools/test_sarif_parser.py b/unittests/tools/test_sarif_parser.py
index e316ae9fe2..aafa91d09e 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())
@@ -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_sonarqube_parser.py b/unittests/tools/test_sonarqube_parser.py
index 16b80aa9eb..ef4912510b 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_stackhawk_parser.py b/unittests/tools/test_stackhawk_parser.py
index da043f9410..7f63ea1d45 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:
diff --git a/unittests/tools/test_tenable_parser.py b/unittests/tools/test_tenable_parser.py
index 7be782c49e..6fee8e1c8f 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())
@@ -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)
diff --git a/unittests/tools/test_trivy_operator_parser.py b/unittests/tools/test_trivy_operator_parser.py
index 8ac6b5f318..5e4a71558d 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)
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 0000000000..30176474f7
--- /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 0000000000..c9793ad65e
--- /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 |
+ H4sIAAAAAAAAA7xW+U8jNxT+V6z5qaVJ5shBGKmqthBaWkRTCCAtRciZeZl447GntockZfnf+zxH
+ wpHZFVRdIYXx8e7vfX4PDqwyKmIndBSIGBTExwx4rFuCpqBbOppDSlsyA0UNk0K3IGYmBUNb0ZyK
+ BLhMWvegNJ5BfA6ZAg3CVHejXBuZzqzCO9/zfK+j4O8ctJmsMxgrGhkWgdNymLXvd4P9A1xo4DNc
+ zo3JdOi6McwgMrH8JDvUcKo1o6IjwLhoybg0Y27gMq1zcGsFC1ij/NlkdDFp+/1BgFuFC9oJHxyN
+ vuU6ogYSqdZlDDGuUCLwgl7b99pdb+IPw34v7Pqd4f7wB/Tbs05aIwYdL9S800kr76I+z3pVhl0t
+ YtCRYplNHO5+IDqlnLdIzLRhIjIkYxABkTOylGrRsdKRFJeKv9GLXDBbLsrv6D01VLn3DJZu4dbW
+ werI97r+8CfN/oEfUyx7nqJVCws0OaF6YWuVT439CmeUa2g5peAJxlXItpw5Q+CoaL4+hXtAX73H
+ lmMYIitDlDihyDFG5wVMul59kCn5CSN6Z8Ir6SLdRQHrdNvFE5Bso7oUzBhUoJ2NbYvU34u7Ws7M
+ kiqLV83SjDN0OH4ROdajQFlvuOoN3+juFypTR7KpS8/bRzeC3iro/b9WyuoXWESD/mDlD76FwVVt
+ sRususG3sFgB/PHxNRz9JpwG9cGMra5KDsTq39y+vtmtb9IkUZAg33y1Cfr1AUYmeV7ywu6rg6aD
+ /YaDoPFg2HRw8NqdkjbLXUtKxQvhhG0fl9Tgw1ES7tsbt6TzLYG7pTpl27L4PJS5TZxvSfnabjCR
+ OKFROTxWPG21KRaVWXt4tWc9w6t6LnMeHzGdcbquWhm30S1zhZix7V1lQwEGa/lj1yPR9/36kXiZ
+ tg2VvTxoAlWwAVWmmFTMrN+ZxFrc7b3trWApTUC7VkLXShhucLns6PtkS5anclmTas953TbBBvOc
+ TsHS4o7GsGyyMw1+E0L9oc3HnOpRxqJTJhbH9uQIMju9iKiuYlHbZXG22RFSjHB4oVMO50B1iQxV
+ fTnj08tfTs7uTk8OR2cXo7vR+fkf5xgfdqnGhOCFyRzIGPlfGGLtEqaJFHxNkEsYt0qJkeQ3pigZ
+ K0iRTEiuEbOdXZziYzs53mfmefFAhE75JmLtMPnbnnrGFViGhAnKX16qZq8qvQXOOXpXrW1dEwGb
+ 23lmm7YJxwOvW+O4HJPeCb1SePPuPp9s3obGLdx+ptECh80acrXy0tZhNc/9J4frodCtZ7OgHhME
+ WKhHkkt1Vnoz5Tm0E4WssR2JJDmSZbFlmuE4LMxu0Pc3pPClwr4UaiKM/qbPnuf5L7H925sww2Ev
+ JDcfaeaH5FDKBQNyzQwSoCEXEOUKyDGnyWebNswalxHlc6lNOPSGnjtjIkaOdYNu77ZQeFRkFQP+
+ JInFW7hHvipJvsOf7wvxC5wGLTmhGNJI5eRRDuQIM4CbxzDtkGC/RRCm/U0Uh9cjPLvBf+2B3ytc
+ tRWOltBJmVHQkSpxEeDUFp3hLGcbw8WrnblJeeF4qefK6rkUCyGXT7M0VjLOcToYiQRbPsUCuhMs
+ i7VZpAgdJr/KZdvIhjRllYLglrjkxteG/JlTZUCRrcoGUdja9Avpjx/G5CKiouG+HVPdA38T1JMw
+ LtbaQKoxjDiTDFG4Fxb7RYVswlLKhGYGOghUzJeeTyVVcdONQuu/AAAA///sWVFL5DAQ/itBEBRs
+ bbvdVQ/EW/AO7kFOFE4QYcm2Wbfcblqa1iqe/91vkmysdesdcogPgg+1SSYz05lvvpltyz9+ijOS
+ PGaJCSeAMpsKIZkSFWtsbFVATWXia4b42mHNPEvmbCm4VFjkZoeVAHOv5FQwniSAXZGym4yzGpmU
+ lHcFcAv7pBSmgvstjU7wqa91z0kKXcyFpPhi3MnN0ZLCImhCZoEeskzO8nKpz7C8JDDnWFWEpcDx
+ 30LukGIQnlXM0ATGFw2/IxNZwbV2tUJkMy5ZS0E0lFIsfHxwRTY7C40PriQ5gW4jH0EDq6FaqUgC
+ VQ0HrdW0ZXPL+jPkIsoSXEbW23BpmsbPG64KnRRIQnHrF/NCBzQumUDmxN494RV40bRGSE22fl6M
+ z0+98xMPFVznqrukyCmSKRm2eLrM5Dbb2v6DQFlU+ReE4UvyE/aRnzBuA2BVAtk1gSNK1t3qaHBn
+ IehbiB1T7Z5wXEN/C82s1m/s4x5BHzsO3J3PanYX0uFnnswpx01dUPVyyalobfwNlsm5REHz8o0V
+ jqjIEdKKiPOP9HA44/EgDaf7cTCcwoC9vYMwikZUUN0m3PDKNkFffpymuANVbuNJB8/2WF9d9JHQ
+ V7tjkxs+yqnepmHJdmjDMApjEQZiEKUHo2SQDPfCZH+YpgEfzUKxf5Qeaimbg/Fm9B1/5py35NLC
+ qeeZV8qvldfAI17kE077RT1dZAm5zCs4V+QxnNdlA3wRj8en3sgvJOnf7bQ/vsbdVv3ja9xt9T+6
+ xgCr1HSOlhO2OdGpnVxRPhGKm+7UAN4luCG2f6vLvBC7l4CiZP6UeDRwwqrLZLrHjtnW08m4D2bj
+ PsoYux6ztKj+iSrvHz+fqPIeGn+iSi+qdFHh5ZASul+bVLynebh9DnBhXnE7ze9Kcdyuu9BH2IJe
+ mOobDgXRWiAMnAFdQb2ErlfZPqY36F1wFFDIm6zMpaF55lVa29+YzL//4tabvPp/81MjzAnFTejV
+ fuV6ErQaeiIRjMr3q0dbbt6sgP49bncld2djyW/PhKoXJLhlrJ7hlNW4MobTJJnmPGS6e//8cPTs
+ 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 |
+ H4sIAAAAAAAAA7xW+2/bNhD+Vwj9tGW29fAjjoBh6BJnyxZkWeIkQLMiYKSzzJoiVZKK7aX533ek
+ JLt5qEMyrCiQWjze67vvjnfvwaqgIvViT4FIQUF6yICnuiNoDrqjkznktCMLUNQwKXQHUmZyMLST
+ zKnIgMuscwdKowzSMygUaBCmvpuU2sh8Zg3ehEEQBj0Fn0rQZrou4FTRxLAEvI7HrP+wH+3u4YcG
+ PsPPuTGFjn0/hRkkJpUfZY8aTrVmVPQEGB89GZ8WzI98pnUJfmNgAWvUP5lOzqfdcDiK8MiFoL34
+ 3tMYW6kTaiCTal3lkOIXakRBNOiGQbcfTMNxPBzE/bA33h3/gHEHNkjrxGDgzswbg7T6PtoLbFRV
+ 2vVHCjpRrLDA4ek7onPKeYekTBsmEkMKBgkQOSNLqRY9q51IcaH4K6MoBbPlovyG3lFDlX/HYOm7
+ sLYB1qIw6IfjnzT7G37Msexljl4tLdDllOqFrVV5a+yveEa5ho5XKR5hXk63480ZEkcl8/Ux3AHG
+ Gjx0PMOQWQWyxItFiTl6T2jSDxpBoeRHzOiNgNfaDm5XwAZu+/EFSbZZXQhmDBrQ3sa3Zerv7q6W
+ M7OkyvJVs7zgDANOn2SO9XAsG4xXg/Erw/1KZZpMNnUZBLsYRjRYRYP/10tVfcdFdBiOVuHoWzhc
+ NR770aoffQuPNcEfHp7TMWzjadQIZmx1Wc1ArP71h+c3+81NmmUKMpw3z5oAE5C8rNr/ZXfDNsGo
+ TbDbIohaBeM2wd7zOKuxWZ3aoeReCC/uhvWstCVRLKlSun92ZhsF0dZzWfL0gOmC03XdTni8pAaf
+ nmpkv771qwdh+wT4lTllG9v93Jelhd6FemUPmMi82KjS+kaj5hI5Y9u7RkMBJmvnx0uPxDAMm0fi
+ KWybUfZU0EaqaEOqQjGpmFm/EYJG3R+87q1gOc1A+1ZDN0YYHnC57Om7bDssj+WyGaoD73nbRBvO
+ c3oLdiy+0Bh2mrwIQ9jG0HBs8ZhTPSlYcszE4tBKDqCw24tIGgY5Xi2dbHMipJjg8kJvOZwB1RUr
+ Vf3LOz2++OXo5Ob4aH9ycj65mZyd/XGG+WGXagQEL0znQE5x/gtDrF/CNJGCrwnOEsatUWIk+Y0p
+ Sk4V5DhMSKmRcb2XZkqI7eQFn1kQpCMRe9WbiLVD8Lc99WhWYBkyJih/eqnevWp4He85Rld/27pm
+ Aja3y8I2bRuPR0G/4XG1Jr2RepXy5t19vNm8jo1buv1MkwUumw3lGuOVr/16n/tPATdLod/sZlGz
+ JgiwVE8kl+qkiuaWl9DNFE6s7UokyYGsii3zAtdhYV4m/bBtKAw3Q+FrFX+qtOmzxzj/Jbb/dqbM
+ cNiJyfV7WoQx2ZdywYBcMYPD15BzSEoF5JDT7LOFDVHjMqF8LrWJx8E48GdMpDgh/ag/+OAMHjhU
+ MeGPkli+xTvkXzXJd/jne6d+jtugHU6ohmOkDvKgBHKAieLhIdz2SLTbIUjT4SaL/asJyq7xv+4o
+ HLhQbYWTJfRyZhT0pMp8JDi1RWe4y9nG8PFqb25y7gKv7FxaOxdiIeTyS5ROlUxL3A4mIsOWz7GA
+ /hTRtz4dRBgw+VUuu0a2wFTUBqIPxCfXoTbkz5IqA4psTbaowtZn6LTfvzsl5wkVLfftmurvhZuk
+ vkjjfK0N5BrTSAvJkIU7sTt3FbKA5ZQJzQz0kKiIl57fSqrSthvO6j8AAAD//+xZbUvcQBD+K4sg
+ KJiY5HKnFsQe2EI/SEWhggjHXrLnhd5tQl6MYu+/95ndvTWmt7ZIET8IfojZ3dmZycwzz8x15Z8+
+ xRlJHrNEhxNAmU2FkKwSNWtNbNVAzUrH1wzxtcfaeZbM2VJwWWGR6x1GAsy9kVPBeJIAdkXK7jLO
+ GmRSUj4UwC3sk1Jo9uB3NDrDp75VPScpdDUXkuKLcSs3R0sKi6AJmQV6yDI5y8ulOsPyksCcY7Ui
+ LAWO/xRyjxSD8KxmmqIwvmj5A5nICq60aypENuOSdRREQynFwscHr8hma6H2wY0kJ9Bt5CNoYDSs
+ 1iqSwKqBgzZq2rG5Y/0FchFlCS4j6024tG3r5y2vCpUUSEJx7xfzQgU0LplA5sTcPeE1ONm0QUhN
+ dr5fjS/PvcszDxVc5aq9pMgpkikZdni6zOQu29n9hUBZ1PknhOGf5Cd04VwYd3GuLoHsin4RHexv
+ dZHewLUQW6baP+HiGoHlGuojKcq1eaOLHQf2TriSJ3NKY1OAuzW8j+RVs1xyKlpbf4Nlci5R0Lx8
+ ZYUjKnKCtCLa+y09Hs54PEjD6WEcDKcw4ODgKIyiERVUuwk3vLBN0JcfpynuQJXbetLBMz3WZxt9
+ JPTF7ljnho9yqrYpWDId2jCMwliEgRhE6dEoGSTDgzA5HKZpwEezUByepMdKyvZgvB19xZ8+5y25
+ NHDqefpV5TeV18IjXuQTTvtFM11kCbnMKzivyGM4r8oG+CIeT8+9kV9I0r/fab9/jfut+vvXuN/q
+ v3eNAVap7loNJ+xyonMzuaJ8IhTXvaUGvGtwQ2z/0pR5IfavgTjJ/CnxaOCEVZvJdI8Zs22mk7EL
+ ZmNXjxnbHrM0qP6BKm8fPx+o8hYaf6CKE1X6qGAbyf6CpWuWzcCoW52jjzQoN88BNMlrbsb8fSku
+ whY4YcpJ2KKNQBg4DXANkwgvNi4E1uTewsB1YmApoJB3WZlLzQH1q7QxvzHpf//Fe3d5/f+mn1qY
+ FYqb0Kv9yNUkaD1wRSJolR/Xj6bcvFoB9Xvc/lru3taS31+IqlmQ4I6xaoZT1uNaG06TZJrzkOn2
+ /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