From 69e22de084c9d466171ee60b46bc6c8a163ef005 Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 8 Sep 2025 14:55:32 +0000 Subject: [PATCH 01/16] Update versions in application files --- components/package.json | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/package.json b/components/package.json index bf7bc889846..d96e5e51133 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.50.1", + "version": "2.51.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index eaba06160a6..8365c40bf5d 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.50.1" +appVersion: "2.51.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.206 +version: 1.6.207-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap From 40b74182b3e45a0861334ec22db063c092f6cb76 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Wed, 10 Sep 2025 22:10:18 +0200 Subject: [PATCH 02/16] :tada: Add OXAS-ADV- vulnid (#13129) --- dojo/settings/settings.dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index b71e4e4520c..4562714428d 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1853,6 +1853,7 @@ def saml2_attrib_map_format(din): "NTAP-": "https://security.netapp.com/advisory/", # e.g. https://security.netapp.com/advisory/ntap-20250328-0007 "OPENSUSE-SU-": "https://osv.dev/vulnerability/", # e.g. https://osv.dev/vulnerability/openSUSE-SU-2025:14898-1 "OSV-": "https://osv.dev/vulnerability/", # e.g. https://osv.dev/vulnerability/OSV-2024-1330 + "OXAS-ADV-": "https://cvepremium.circl.lu/vuln/", # e.g. https://cvepremium.circl.lu/vuln/OXAS-ADV-2023-0001 "PAN-SA-": "https://security.paloaltonetworks.com/", # e.g. https://security.paloaltonetworks.com/PAN-SA-2024-0010 "PFPT-SA-": "https://www.proofpoint.com/us/security/security-advisories/", # e.g. https://www.proofpoint.com/us/security/security-advisories/pfpt-sa-0002 "PMASA-": "https://www.phpmyadmin.net/security/", # e.g. https://www.phpmyadmin.net/security/PMASA-2025-1 From e464cbefd9aa15eecd0c76895371173827282a3e Mon Sep 17 00:00:00 2001 From: valentijnscholten Date: Wed, 10 Sep 2025 22:11:12 +0200 Subject: [PATCH 03/16] Allow enabling Django Debug Toolbar via env variable (#12921) * Allow enabling Django Debug Toolbar via env variable * Allow enabling Django Debug Toolbar via env variable * debugtoolbar: fix collectstatic --- Dockerfile.nginx-alpine | 3 +- docker-compose.override.dev.yml | 6 ++++ dojo/settings/settings.dist.py | 44 ++++++++++++++++++++++++ dojo/settings/template-local_settings | 48 +-------------------------- dojo/urls.py | 6 ++++ readme-docs/DOCKER.md | 4 +-- 6 files changed, 61 insertions(+), 50 deletions(-) diff --git a/Dockerfile.nginx-alpine b/Dockerfile.nginx-alpine index 54eefe0d2ba..8bd948ee65d 100644 --- a/Dockerfile.nginx-alpine +++ b/Dockerfile.nginx-alpine @@ -34,6 +34,7 @@ RUN CPUCOUNT=1 pip3 wheel --wheel-dir=/tmp/wheels -r ./requirements.txt FROM build AS collectstatic +ARG COLLECT_DJANGO_DEBUG_TOOLBAR_STATIC=false RUN apk add nodejs npm RUN npm install -g yarn --force @@ -52,7 +53,7 @@ RUN \ yarn COPY manage.py ./ COPY dojo/ ./dojo/ -RUN env DD_SECRET_KEY='.' python3 manage.py collectstatic --noinput && true +RUN env DD_SECRET_KEY='.' DD_DJANGO_DEBUG_TOOLBAR_ENABLED=${COLLECT_DJANGO_DEBUG_TOOLBAR_STATIC} python3 manage.py collectstatic --noinput --verbosity=2 && true FROM nginx:1.29.1-alpine3.22@sha256:42a516af16b852e33b7682d5ef8acbd5d13fe08fecadc7ed98605ba5e3b26ab8 ARG uid=1001 diff --git a/docker-compose.override.dev.yml b/docker-compose.override.dev.yml index 8dfa5a9e19b..650d8b14bb6 100644 --- a/docker-compose.override.dev.yml +++ b/docker-compose.override.dev.yml @@ -7,6 +7,7 @@ services: environment: PYTHONWARNINGS: error # We are strict about Warnings during development DD_DEBUG: 'True' + DD_DJANGO_DEBUG_TOOLBAR_ENABLED: 'True' DD_ADMIN_USER: "${DD_ADMIN_USER:-admin}" DD_ADMIN_PASSWORD: "${DD_ADMIN_PASSWORD:-admin}" DD_EMAIL_URL: "smtp://mailhog:1025" @@ -33,6 +34,11 @@ services: DD_ADMIN_USER: "${DD_ADMIN_USER:-admin}" DD_ADMIN_PASSWORD: "${DD_ADMIN_PASSWORD:-admin}" nginx: + build: + args: + COLLECT_DJANGO_DEBUG_TOOLBAR_STATIC: 'True' + environment: + DD_DJANGO_DEBUG_TOOLBAR_ENABLED: 'True' volumes: - './dojo/static/dojo:/usr/share/nginx/html/static/dojo' postgres: diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 4562714428d..77f629e24ef 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -29,6 +29,7 @@ # Set casting and default values DD_SITE_URL=(str, "http://localhost:8080"), DD_DEBUG=(bool, False), + DD_DJANGO_DEBUG_TOOLBAR_ENABLED=(bool, False), DD_TEMPLATE_DEBUG=(bool, False), DD_LOG_LEVEL=(str, ""), DD_DJANGO_METRICS_ENABLED=(bool, False), @@ -356,6 +357,7 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param # False if not in os.environ DEBUG = env("DD_DEBUG") +DJANGO_DEBUG_TOOLBAR_ENABLED = env("DD_DJANGO_DEBUG_TOOLBAR_ENABLED") TEMPLATE_DEBUG = env("DD_TEMPLATE_DEBUG") # Hosts/domain names that are valid for this site; required if DEBUG is False @@ -1942,3 +1944,45 @@ def saml2_attrib_map_format(din): warnings.filterwarnings("ignore", "The FORMS_URLFIELD_ASSUME_HTTPS transitional setting is deprecated.") FORMS_URLFIELD_ASSUME_HTTPS = True # Inspired by https://adamj.eu/tech/2023/12/07/django-fix-urlfield-assume-scheme-warnings/ + +if DEBUG: + # adding DEBUG logging for all of Django. + LOGGING["loggers"]["root"] = { + "handlers": ["console"], + "level": "DEBUG", + } + +if DJANGO_DEBUG_TOOLBAR_ENABLED: + + INSTALLED_APPS += ( + "debug_toolbar", + ) + + MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE] + + def show_toolbar(request): + return True + + DEBUG_TOOLBAR_CONFIG = { + "SHOW_TOOLBAR_CALLBACK": show_toolbar, + "INTERCEPT_REDIRECTS": False, + "SHOW_COLLAPSED": True, + } + + DEBUG_TOOLBAR_PANELS = [ + # 'ddt_request_history.panels.request_history.RequestHistoryPanel', # Here it is + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.templates.TemplatesPanel", + # 'debug_toolbar.panels.staticfiles.StaticFilesPanel', + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + # 'debug_toolbar.panels.logging.LoggingPanel', + "debug_toolbar.panels.redirects.RedirectsPanel", + "debug_toolbar.panels.profiling.ProfilingPanel", + # 'cachalot.panels.CachalotPanel', + ] diff --git a/dojo/settings/template-local_settings b/dojo/settings/template-local_settings index 337027bff92..c710201d620 100644 --- a/dojo/settings/template-local_settings +++ b/dojo/settings/template-local_settings @@ -1,21 +1,7 @@ # local_settings.py # this file will be included by settings.py *after* loading settings.dist.py -# this example configures the django debug toolbar and sets some loglevels to DEBUG - -from django.urls import re_path -from django.conf.urls import include - -# UPDATE: Adding debug_toolbar to to INSTALLED_APPS here prevents the nginx container from generating the correct static files -# So add debug_toolbar to INSTALLED_APPS in settings.dist.py and rebuild to get started with the debug_toolbar. -# Thje middleware and other config can remain in this file (local_settings.py) to avoid chance of conflicts on upgrades. -INSTALLED_APPS += ( -# 'debug_toolbar', -) - -MIDDLEWARE = [ - 'debug_toolbar.middleware.DebugToolbarMiddleware', -] + MIDDLEWARE +# this example sets some loglevels to DEBUG # adding DEBUG logging for all of Django. LOGGING['loggers']['root'] = { @@ -27,35 +13,3 @@ LOGGING['loggers']['root'] = { # output DEBUG logging for deduplication # LOGGING['loggers']['dojo.specific-loggers.deduplication']['level'] = 'DEBUG' - - -def show_toolbar(request): - return True - - -DEBUG_TOOLBAR_CONFIG = { - "SHOW_TOOLBAR_CALLBACK": show_toolbar, - "INTERCEPT_REDIRECTS": False, - "SHOW_COLLAPSED": True, -} - -DEBUG_TOOLBAR_PANELS = [ - # 'ddt_request_history.panels.request_history.RequestHistoryPanel', # Here it is - 'debug_toolbar.panels.versions.VersionsPanel', - 'debug_toolbar.panels.timer.TimerPanel', - 'debug_toolbar.panels.settings.SettingsPanel', - 'debug_toolbar.panels.headers.HeadersPanel', - 'debug_toolbar.panels.request.RequestPanel', - 'debug_toolbar.panels.sql.SQLPanel', - 'debug_toolbar.panels.templates.TemplatesPanel', - # 'debug_toolbar.panels.staticfiles.StaticFilesPanel', - 'debug_toolbar.panels.cache.CachePanel', - 'debug_toolbar.panels.signals.SignalsPanel', - 'debug_toolbar.panels.logging.LoggingPanel', - 'debug_toolbar.panels.redirects.RedirectsPanel', - 'debug_toolbar.panels.profiling.ProfilingPanel', - # 'cachalot.panels.CachalotPanel', -] - -import debug_toolbar -EXTRA_URL_PATTERNS = [re_path(r"^__debug__/", include(debug_toolbar.urls))] diff --git a/dojo/urls.py b/dojo/urls.py index f7467f477f4..06ae8eb86ee 100644 --- a/dojo/urls.py +++ b/dojo/urls.py @@ -281,3 +281,9 @@ def drf_spectacular_preprocessing_filter_spec(endpoints): if path.startswith("/api/v2/"): filtered.append((path, path_regex, method, callback)) return filtered + + +if hasattr(settings, "DJANGO_DEBUG_TOOLBAR_ENABLED"): + if settings.DJANGO_DEBUG_TOOLBAR_ENABLED: + from debug_toolbar.toolbar import debug_toolbar_urls + urlpatterns += debug_toolbar_urls() diff --git a/readme-docs/DOCKER.md b/readme-docs/DOCKER.md index 30ebdaa57c2..0f9c5bcedf2 100644 --- a/readme-docs/DOCKER.md +++ b/readme-docs/DOCKER.md @@ -176,8 +176,8 @@ Or you can modify `settings.dist.py` directly, but this adds the risk of having ``` ## Debug Toolbar -In the `dojo/settings/template-local_settings.py` you'll find instructions on how to enable the [Django Debug Toolbar](https://github.com/jazzband/django-debug-toolbar). -This toolbar allows you to debug SQL queries, and shows some other interesting information. +The [Django Debug Toolbar](https://github.com/jazzband/django-debug-toolbar) can be enabled via the `DD_DJANGO_DEBUG_TOOLBAR_ENABLED` environment variable. +This toolbar allows you to debug SQL queries, and shows some other interesting information. Do NOT enable this in Production environments. # Explicit Versioning From 1336ba584d766f234d10e04d8f53edcfa5374e13 Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:50:58 +0200 Subject: [PATCH 04/16] fix(helm): uwsgi tuning (#13146) --- helm/defectdojo/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/defectdojo/values.yaml b/helm/defectdojo/values.yaml index 5d073474a08..acd06793da0 100644 --- a/helm/defectdojo/values.yaml +++ b/helm/defectdojo/values.yaml @@ -283,8 +283,8 @@ django: cpu: 2000m memory: 512Mi appSettings: - processes: 2 - threads: 2 + processes: 4 + threads: 4 # maxFd: 102400 # Uncomment to set the maximum number of file descriptors. If not set will be detected by uwsgi enableDebug: false # this also requires DD_DEBUG to be set to True certificates: From 35bc518c7e253dc84d74567a934179fc60fe5d88 Mon Sep 17 00:00:00 2001 From: valentijnscholten Date: Thu, 11 Sep 2025 19:51:09 +0200 Subject: [PATCH 05/16] Update to Django 5.1.12 (#13148) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 350b8d03dcc..dc0f1f2f8a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ django-slack==5.19.0 git+https://github.com/DefectDojo/django-tagging@develop#egg=django-tagging django-watson==1.6.3 django-prometheus==2.4.1 -Django==5.1.11 +Django==5.1.12 djangorestframework==3.16.1 html2text==2025.4.15 humanize==4.13.0 From 976f72cc4ca69d7e08cd926e8267b87a79c2a6d4 Mon Sep 17 00:00:00 2001 From: kiblik <5609770+kiblik@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:55:34 +0200 Subject: [PATCH 06/16] feat(social): Add SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT (#13150) --- .../content/en/customize_dojo/user_management/configure_sso.md | 1 + dojo/context_processors.py | 1 + dojo/settings/settings.dist.py | 3 +++ dojo/templates/dojo/login.html | 2 +- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/content/en/customize_dojo/user_management/configure_sso.md b/docs/content/en/customize_dojo/user_management/configure_sso.md index 45bd70c3279..b66fa08dbf7 100644 --- a/docs/content/en/customize_dojo/user_management/configure_sso.md +++ b/docs/content/en/customize_dojo/user_management/configure_sso.md @@ -564,6 +564,7 @@ You can also optionally set the following variables: DD_SOCIAL_AUTH_OIDC_AUTHORIZATION_URL=(str, ''), DD_SOCIAL_AUTH_OIDC_USERINFO_URL=(str, ''), DD_SOCIAL_AUTH_OIDC_JWKS_URI=(str, ''), + DD_SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT=(str, "Login with OIDC"), {{< /highlight >}} Once these variables have been set, restart DefectDojo. Log In With OIDC should now be added to the DefectDojo login page. diff --git a/dojo/context_processors.py b/dojo/context_processors.py index 7a3c84af035..39385ef3440 100644 --- a/dojo/context_processors.py +++ b/dojo/context_processors.py @@ -12,6 +12,7 @@ def globalize_vars(request): "FORGOT_USERNAME": settings.FORGOT_USERNAME, "CLASSIC_AUTH_ENABLED": settings.CLASSIC_AUTH_ENABLED, "OIDC_ENABLED": settings.OIDC_AUTH_ENABLED, + "SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT": settings.SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT, "AUTH0_ENABLED": settings.AUTH0_OAUTH2_ENABLED, "GOOGLE_ENABLED": settings.GOOGLE_OAUTH_ENABLED, "OKTA_ENABLED": settings.OKTA_OAUTH_ENABLED, diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 77f629e24ef..fb2d74e82e6 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -121,6 +121,7 @@ DD_SOCIAL_AUTH_OIDC_AUTHORIZATION_URL=(str, ""), DD_SOCIAL_AUTH_OIDC_USERINFO_URL=(str, ""), DD_SOCIAL_AUTH_OIDC_JWKS_URI=(str, ""), + DD_SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT=(str, "Login with OIDC"), DD_SOCIAL_AUTH_AUTH0_OAUTH2_ENABLED=(bool, False), DD_SOCIAL_AUTH_AUTH0_KEY=(str, ""), DD_SOCIAL_AUTH_AUTH0_SECRET=(str, ""), @@ -620,6 +621,8 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param SOCIAL_AUTH_OIDC_USERINFO_URL = value if value := env("DD_SOCIAL_AUTH_OIDC_JWKS_URI"): SOCIAL_AUTH_OIDC_JWKS_URI = value +if value := env("DD_SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT"): + SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT = value AUTH0_OAUTH2_ENABLED = env("DD_SOCIAL_AUTH_AUTH0_OAUTH2_ENABLED") SOCIAL_AUTH_AUTH0_KEY = env("DD_SOCIAL_AUTH_AUTH0_KEY") diff --git a/dojo/templates/dojo/login.html b/dojo/templates/dojo/login.html index 430539f4930..fe54191f2a6 100644 --- a/dojo/templates/dojo/login.html +++ b/dojo/templates/dojo/login.html @@ -49,7 +49,7 @@

{% trans "Login" %}

{% if OIDC_ENABLED is True %} {% endif %} From 00ac181d1cc80d0f3f8e2d57f64fcd31443469f7 Mon Sep 17 00:00:00 2001 From: Paul Osinski <42211303+paulOsinski@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:42:21 -0400 Subject: [PATCH 07/16] add new opensource page (#13151) Co-authored-by: Paul Osinski --- docs/content/en/about_defectdojo/contact_defectdojo_support.md | 2 +- docs/layouts/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/en/about_defectdojo/contact_defectdojo_support.md b/docs/content/en/about_defectdojo/contact_defectdojo_support.md index bdbaeeaa95e..c80732f3be4 100644 --- a/docs/content/en/about_defectdojo/contact_defectdojo_support.md +++ b/docs/content/en/about_defectdojo/contact_defectdojo_support.md @@ -16,7 +16,7 @@ For Open-Source users, the quickest way to get help is through the [OWASP Slack To report a bug, issues can be raised on our [GitHub](https://github.com/DefectDojo/django-DefectDojo). -See our [Community Site](https://defectdojo.com/community) for more information. +See our [Community Site](https://defectdojo.com/open-source) for more information. ## DefectDojo Pro Support diff --git a/docs/layouts/index.html b/docs/layouts/index.html index 45f99b1637a..05a254feae1 100644 --- a/docs/layouts/index.html +++ b/docs/layouts/index.html @@ -44,7 +44,7 @@

Create Reports

Join the Dojo community

-

Check out live events, upcoming features and connect with other security professionals on our Community Page.

+

Check out live events, upcoming features and connect with other security professionals on our Community Page.

Sign up for a trial

From 2fe00d60d621734bb920fed1846fb3cd1065abca Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 12 Sep 2025 04:58:26 +0200 Subject: [PATCH 08/16] :tada: Add fix_available information to mend #12633 (#13142) * :tada: Add fix_available information to mend #12633 * fix --- dojo/tools/mend/parser.py | 6 ++++++ unittests/tools/test_mend_parser.py | 2 ++ 2 files changed, 8 insertions(+) diff --git a/dojo/tools/mend/parser.py b/dojo/tools/mend/parser.py index c71ed89e2f3..37bef7667cc 100644 --- a/dojo/tools/mend/parser.py +++ b/dojo/tools/mend/parser.py @@ -77,6 +77,7 @@ def _build_common_output(node, lib_name=None): ransomware_used = node.get("malicious", None) known_exploited = node.get("exploitable", None) component_path = node["component"].get("path", None) + fix_available = False if component_path: locations.append(component_path) if "topFix" in node: @@ -91,6 +92,7 @@ def _build_common_output(node, lib_name=None): + topfix_node.get("fixResolution", "") + "\n" ) + fix_available = True except Exception: logger.exception("Error handling topFix node.") elif "library" in node: @@ -116,6 +118,7 @@ def _build_common_output(node, lib_name=None): component_name = node["library"].get("artifactId") component_version = node["library"].get("version") cvss3_score = node.get("cvss3_score", None) + fix_available = False if "topFix" in node: try: topfix_node = node.get("topFix") @@ -123,10 +126,12 @@ def _build_common_output(node, lib_name=None): topfix_node.get("date"), topfix_node.get("fixResolution"), ) + fix_available = True except Exception: logger.exception("Error handling topFix node.") else: description = node.get("description", "Unknown") + fix_available = False cve = node.get("name") title = "CVE-None | " + lib_name if cve is None else cve + " | " + lib_name @@ -208,6 +213,7 @@ def _build_common_output(node, lib_name=None): impact=impact if impact is not None else None, steps_to_reproduce="**Locations Found**: " + ", ".join(locations) if locations is not None else None, kev_date=kev_date if kev_date is not None else None, + fix_available=fix_available, ) # only overwrite default values if they are not None #12989 if known_exploited is not None: diff --git a/unittests/tools/test_mend_parser.py b/unittests/tools/test_mend_parser.py index f8dc883155b..136fb393f72 100644 --- a/unittests/tools/test_mend_parser.py +++ b/unittests/tools/test_mend_parser.py @@ -21,6 +21,7 @@ def test_parse_file_with_one_vuln_has_one_findings(self): self.assertEqual("CVE-2019-9658", finding.unsaved_vulnerability_ids[0]) self.assertEqual("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", finding.cvssv3) self.assertEqual(5.3, finding.cvssv3_score) + self.assertEqual(True, finding.fix_available) def test_parse_file_with_multiple_vuln_has_multiple_finding(self): with (get_unit_tests_scans_path("mend") / "okhttp_many_vuln.json").open(encoding="utf-8") as testfile: @@ -44,6 +45,7 @@ def test_parse_file_with_one_sca_vuln_finding(self): finding = list(findings)[0] self.assertEqual("**Locations Found**: D:\\MendRepo\\test-product\\test-project\\test-project-subcomponent\\path\\to\\the\\Java\\commons-codec-1.6_donotuse.jar", finding.steps_to_reproduce) self.assertEqual("WS-2019-0379 | commons-codec-1.6.jar", finding.title) + self.assertEqual(True, finding.fix_available) def test_parse_file_with_no_vuln_has_no_findings_platform(self): with (get_unit_tests_scans_path("mend") / "mend-sca-platform-api3-no-findings.json").open(encoding="utf-8") as testfile: From 8fb9fd7d8fc3616874b244d6fbebb341b96cc745 Mon Sep 17 00:00:00 2001 From: Filipe Pina <636320+fopina@users.noreply.github.com> Date: Fri, 12 Sep 2025 17:05:27 +0100 Subject: [PATCH 09/16] fortify fpr_parser: allow optional fields to be optional (#13160) --- dojo/tools/fortify/fpr_parser.py | 41 ++++++++++-------- .../scans/fortify/hello_world_no_metainfo.fpr | Bin 0 -> 24703 bytes unittests/tools/test_fortify_parser.py | 24 ++++++++++ 3 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 unittests/scans/fortify/hello_world_no_metainfo.fpr diff --git a/dojo/tools/fortify/fpr_parser.py b/dojo/tools/fortify/fpr_parser.py index f348aa265d6..21c981eb75f 100644 --- a/dojo/tools/fortify/fpr_parser.py +++ b/dojo/tools/fortify/fpr_parser.py @@ -133,14 +133,14 @@ def convert_vulnerabilities_to_findings(self, root: Element, audit_log: Element, finding = Finding(test=test, static_finding=True) finding.active, finding.false_p = self.compute_status(related_data, vuln_data) - finding.title = self.format_title(vuln_data, snippet, description, rule) + finding.title = self.format_title(vuln_data, snippet) finding.description = self.format_description(vuln_data, snippet, description, rule) finding.mitigation = self.format_mitigation(vuln_data, snippet, description, rule) - finding.severity = self.compute_severity(vuln_data, snippet, description, rule) + finding.severity = self.compute_severity(vuln_data, rule) finding.impact = self.format_impact(related_data, vuln_data) finding.file_path = vuln_data.source_location_path - finding.line = int(self.compute_line(vuln_data, snippet, description, rule)) + finding.line = int(self.compute_line(vuln_data, snippet)) finding.unique_id_from_tool = vuln_data.instance_id findings.append(finding) @@ -225,26 +225,28 @@ def parse_description_information(self, description: Element) -> DescriptionData def parse_rule_information(self, rule: Element) -> RuleData: """Parse the rule information and return a RuleData object.""" rule_data = RuleData() - rule_data.accuracy = rule.findtext("Group[@name='Accuracy']", None, self.namespaces) - rule_data.impact = rule.findtext("Group[@name='Impact']", None, self.namespaces) - rule_data.probability = rule.findtext("Group[@name='Probability']", None, self.namespaces) - rule_data.impact_bias = rule.findtext("Group[@name='ImpactBias']", None, self.namespaces) - rule_data.confidentiality_impact = rule.findtext("Group[@name='ConfidentialityImpact']", None, self.namespaces) - rule_data.integrity_impact = rule.findtext("Group[@name='IntegrityImpact']", None, self.namespaces) - rule_data.remediation_effort = rule.findtext("Group[@name='Recommendations']", None, self.namespaces) - logger.debug(f"Rule Impact: {rule_data.impact}") + if rule is not None: + rule_data.accuracy = rule.findtext("Group[@name='Accuracy']", None, self.namespaces) + rule_data.impact = rule.findtext("Group[@name='Impact']", None, self.namespaces) + rule_data.probability = rule.findtext("Group[@name='Probability']", None, self.namespaces) + rule_data.impact_bias = rule.findtext("Group[@name='ImpactBias']", None, self.namespaces) + rule_data.confidentiality_impact = rule.findtext("Group[@name='ConfidentialityImpact']", None, self.namespaces) + rule_data.integrity_impact = rule.findtext("Group[@name='IntegrityImpact']", None, self.namespaces) + rule_data.remediation_effort = rule.findtext("Group[@name='Recommendations']", None, self.namespaces) + logger.debug(f"Rule Impact: {rule_data.impact}") return rule_data - def format_title(self, vulnerability, snippet, description, rule) -> str: + def format_title(self, vulnerability, snippet) -> str: # defaults for when there is no snippet (shouldn't happen, future improvement: parser might also parse ReplacementDefinitions and/or Context elements) file_name = vulnerability.source_location_path.split("/")[-1] - line = self.compute_line(vulnerability, snippet, description, rule) + line = self.compute_line(vulnerability, snippet) return f"{vulnerability.vulnerability_type} - {file_name}: {line} ({vulnerability.class_id})" def format_description(self, vulnerability, snippet, description, rule) -> str: desc = f"##Catagory: {vulnerability.vulnerability_type}\n" - desc += f"###Abstract:\n{description.abstract}\n" + if description: + desc += f"###Abstract:\n{description.abstract}\n" desc += f"**SourceLocationPath:** {vulnerability.source_location_path}\n" desc += f"**SourceLocationLine:** {vulnerability.source_location_line}\n" @@ -258,7 +260,8 @@ def format_description(self, vulnerability, snippet, description, rule) -> str: "leads to this finding. \n") desc += f"###Snippet:\n**File: {snippet.file_name}: {snippet.start_line}**\n```\n{snippet.text}\n```\n" - desc += f"##Explanation:\n {description.explanation}" + if description: + desc += f"##Explanation:\n {description.explanation}" desc += f"##Details: {vulnerability.instance_id}\n" desc += f"**InstanceID:** {vulnerability.instance_id}\n" @@ -273,14 +276,14 @@ def format_description(self, vulnerability, snippet, description, rule) -> str: def format_mitigation(self, vulnerability, snippet, description, rule) -> str: mitigation = "" - if description.recommendations: + if description and description.recommendations: mitigation += f"###Recommendation:\n {description.recommendations}\n" - if description.tips: + if description and description.tips: mitigation += f"###Tips:\n {description.tips}" return mitigation - def compute_severity(self, vulnerability, snippet, description, rule) -> str: + def compute_severity(self, vulnerability, rule) -> str: """Convert the the float representation of severity and confidence to a string severity.""" if not rule.impact: logger.debug("No rule impact found, setting severity to Informational") @@ -330,7 +333,7 @@ def compute_status(self, related_data, vulnerability) -> tuple[bool, bool]: return False, True return True, False - def compute_line(self, vulnerability, snippet, description, rule) -> str: + def compute_line(self, vulnerability, snippet) -> str: if snippet and snippet.start_line: return snippet.start_line return vulnerability.source_location_line diff --git a/unittests/scans/fortify/hello_world_no_metainfo.fpr b/unittests/scans/fortify/hello_world_no_metainfo.fpr new file mode 100644 index 0000000000000000000000000000000000000000..c502c41ba9f50cfcf9fc7e0ebe478af76d11894b GIT binary patch literal 24703 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 JK%>7#{{iYbsvQ6T literal 0 HcmV?d00001 diff --git a/unittests/tools/test_fortify_parser.py b/unittests/tools/test_fortify_parser.py index c0d3b3d191c..144eb502615 100644 --- a/unittests/tools/test_fortify_parser.py +++ b/unittests/tools/test_fortify_parser.py @@ -154,3 +154,27 @@ def test_fortify_fpr_suppressed_finding(self): self.assertFalse(finding.active) self.assertTrue(finding.false_p) self.assertEqual("Threaded Comments:\n2025-03-10T20:52:28.964+05:30 - (testuser): Not an issue. Handled in server config to refer to internal Artifactory\n", finding.impact) + + def test_fortify_hello_world_fpr_rule_without_metainfo(self): + with (get_unit_tests_scans_path("fortify") / "hello_world_no_metainfo.fpr").open(encoding="utf-8") as testfile: + parser = FortifyParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(4, len(findings)) + # for i in range(len(findings)): + # print(f"{i}: {findings[i]}: {findings[i].severity}") + + with self.subTest(i=0): + finding = findings[0] + self.assertEqual("Password Management - HelloWorld.java: 5 (720E3A66-55AC-4D2D-8DB9-DC30E120A52F)", finding.title) + # Info as rule has no metainfo/impact + self.assertEqual("Informational", finding.severity) + self.assertEqual("A5338E223E737FF81F8A806C50A05969", finding.unique_id_from_tool) + self.assertEqual("src/main/java/hello/HelloWorld.java", finding.file_path) + self.assertEqual(5, finding.line) + with self.subTest(i=1): + finding = findings[1] + self.assertEqual("Password Management - HelloWorld.java: 13 (9C5BD1B5-C296-48d4-B5F5-5D2958661BC4)", finding.title) + self.assertEqual("High", finding.severity) + self.assertEqual("D3166922519EDD92D132761602EB71B4", finding.unique_id_from_tool) + self.assertEqual("src/main/java/hello/HelloWorld.java", finding.file_path) + self.assertEqual(13, finding.line) From 3ab9c062a6963dd5d099627e5d6900e268d88f1c Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:05:34 +0200 Subject: [PATCH 10/16] :tada: Add fix_available information to wpscan #12633 (#13153) --- dojo/tools/wpscan/parser.py | 2 ++ unittests/tools/test_wpscan_parser.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/dojo/tools/wpscan/parser.py b/dojo/tools/wpscan/parser.py index 2ba6b5016b7..261b65220d8 100644 --- a/dojo/tools/wpscan/parser.py +++ b/dojo/tools/wpscan/parser.py @@ -62,8 +62,10 @@ def get_vulnerabilities( if report_date: finding.date = report_date # if there is a fixed version fill mitigation + finding.fix_available = False if vul.get("fixed_in"): finding.mitigation = "fixed in : " + vul["fixed_in"] + finding.fix_available = True # manage CVE if "cve" in vul["references"]: finding.unsaved_vulnerability_ids = [] diff --git a/unittests/tools/test_wpscan_parser.py b/unittests/tools/test_wpscan_parser.py index 994a03b90fa..0aa376e0a48 100644 --- a/unittests/tools/test_wpscan_parser.py +++ b/unittests/tools/test_wpscan_parser.py @@ -107,6 +107,7 @@ def test_parse_file_with_multiple_vuln_in_version(self): self.assertNotEqual("Info", finding.severity) # it is a vulnerability so not 'Info' self.assertEqual("WordPress 2.8.1-4.7.2 - Control Characters in Redirect URL Validation", finding.title) self.assertEqual("fixed in : 4.6.4", finding.mitigation) + self.assertEqual(True, finding.fix_available) self.assertEqual("", finding.get_scanner_confidence_text()) # data are => 100% def test_parse_file_issue5774(self): @@ -123,6 +124,7 @@ def test_parse_file_issue5774(self): self.assertNotEqual("Info", finding.severity) self.assertEqual("All in One SEO Pack <= 2.9.1.1 - Authenticated Stored Cross-Site Scripting (XSS)", finding.title) self.assertEqual("fixed in : 2.10", finding.mitigation) + self.assertEqual(True, finding.fix_available) self.assertEqual(7, finding.scanner_confidence) self.assertEqual("Tentative", finding.get_scanner_confidence_text()) # data are at 30% with self.subTest(i=19): @@ -137,6 +139,7 @@ def test_parse_file_issue5774(self): self.assertNotEqual("Info", finding.severity) self.assertEqual("All in One SEO Pack <= 2.9.1.1 - Authenticated Stored Cross-Site Scripting (XSS)", finding.title) self.assertEqual("fixed in : 2.10", finding.mitigation) + self.assertEqual(True, finding.fix_available) self.assertEqual("Tentative", finding.get_scanner_confidence_text()) # data are at 30% with self.subTest(i=50): From f1562775f39f9442931cf760434b4d8aff277775 Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:05:50 +0200 Subject: [PATCH 11/16] :tada: Add fix_available information to jfrogondemand #12633 (#13124) --- dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py | 7 +++++-- .../tools/test_jfrog_xray_on_demand_binary_scan_parser.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py b/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py index 1fcaee93843..021f3cdcb81 100644 --- a/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py +++ b/dojo/tools/jfrog_xray_on_demand_binary_scan/parser.py @@ -102,10 +102,12 @@ def get_severity_justification(vulnerability): def process_component(component): mitigation = "" impact = "**Impact paths**\n\n- " + fix_available = False fixed_versions = component.get("fixed_versions") if fixed_versions: mitigation = "**Versions containing a fix:**\n\n- " mitigation += "\n- ".join(fixed_versions) + fix_available = True if "impact_paths" in component: refs = [] impact_paths_l1 = component["impact_paths"] @@ -117,7 +119,7 @@ def process_component(component): refs.append(item["full_path"]) if refs: impact += "\n- ".join(sorted(set(refs))) # deduplication - return mitigation, impact + return mitigation, impact, fix_available def get_cve(vulnerability): @@ -158,7 +160,7 @@ def get_item_set(vulnerability): for component_name_with_version, component in vulnerability.get("components", {}).items(): component_name, component_version = get_component_name_version(component_name_with_version) - mitigation, impact = process_component(component) + mitigation, impact, fix_available = process_component(component) title = clean_title(vulnerability["summary"]) # create the finding object @@ -176,6 +178,7 @@ def get_item_set(vulnerability): dynamic_finding=False, cvssv3=cvssv3, vuln_id_from_tool=vuln_id_from_tool, + fix_available=fix_available, ) if vulnerability_ids: finding.unsaved_vulnerability_ids = vulnerability_ids diff --git a/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py b/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py index 6793b392257..668cfed533e 100644 --- a/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py +++ b/unittests/tools/test_jfrog_xray_on_demand_binary_scan_parser.py @@ -19,6 +19,7 @@ def test_parse_file_with_one_vuln(self): self.assertEqual("gav://test", item.component_name) self.assertEqual("CVE-2014-0114", item.unsaved_vulnerability_ids[0]) self.assertEqual("High", item.severity) + self.assertEqual(True, item.fix_available) def test_parse_file_with_many_vulns(self): testfile = (get_unit_tests_scans_path("jfrog_xray_on_demand_binary_scan") / "many_vulns.json").open(encoding="utf-8") From a998a9bd2afce969e5f5f41b786ea8c3988499ed Mon Sep 17 00:00:00 2001 From: Mykhailo Sindieiev <9822870+mykhailo-sindieiev@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:38:20 +0200 Subject: [PATCH 12/16] Generic parser update (#13139) * add new fields to generic parser * add test file * fix missing trailing comma * modify csv parser and add csv test file * remove spaces from blank lines * update parser documentation --- .../parsers/file/generic.md | 86 ++++++++++++++++++- dojo/tools/generic/csv_parser.py | 20 +++++ dojo/tools/generic/json_parser.py | 6 ++ .../generic/generic_report_kev_cvssv4.csv | 2 + .../generic/generic_report_kev_cvssv4.json | 46 ++++++++++ 5 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 unittests/scans/generic/generic_report_kev_cvssv4.csv create mode 100644 unittests/scans/generic/generic_report_kev_cvssv4.json diff --git a/docs/content/en/connecting_your_tools/parsers/file/generic.md b/docs/content/en/connecting_your_tools/parsers/file/generic.md index 8952e83858b..f9c9943378e 100644 --- a/docs/content/en/connecting_your_tools/parsers/file/generic.md +++ b/docs/content/en/connecting_your_tools/parsers/file/generic.md @@ -1,10 +1,12 @@ --- -title: "Generic Findings Import" +title: 'Generic Findings Import' toc_hide: true --- + Import Generic findings in CSV or JSON format. Attributes supported for CSV: + - Date: Date of the finding in mm/dd/yyyy format. - Title: Title of the finding - CweId: Cwe identifier, must be an integer value. @@ -18,13 +20,79 @@ Attributes supported for CSV: - Verified: Indicator if the finding has been verified. Must be empty, TRUE, or FALSE - FalsePositive: Indicator if the finding is a false positive. Must be TRUE, or FALSE. - Duplicate:Indicator if the finding is a duplicate. Must be TRUE, or FALSE -- IsMitigated: Indicator if the finding is mitigated. Must be TRUE, or FALSE +- IsMitigated: Indicator if the finding is mitigated. Must be TRUE, or FALSE - MitigatedDate: Date the finding was mitigated in mm/dd/yyyy format or ISO format +- epss_score: Finding [EPSS score](https://www.first.org/epss/) +- epss_percentile: Finding [EPSS percentile](https://www.first.org/epss/articles/prob_percentile_bins) +- CVSSV3: CVSSv3 verctor of the finding +- CVSSV3_score: CVSSv3 score of the finding +- CVSSV4: CVSSv4 vector of the finding +- CVSSV4_score: CVSSv4 score of the finding +- known_exploited: Indicator if the finding is listed in Known Exploited List. Must be TRUE, or FALSE +- ransomware_used: Indicator if the finding is used in Ransomware. Must be TRUE, or FALSE +- fix_available: Indicator if fix available for the finding. Must be TRUE, or FALSE +- kev_date: Date the finding was added to Known Exploited Vulnerabilities list in mm/dd/yyyy format or ISO format. The CSV expects a header row with the names of the attributes. Date fields are parsed using [dateutil.parse](https://dateutil.readthedocs.io/en/stable/parser.html) supporting a variety of formats such a YYYY-MM-DD or ISO-8601. +The list of supported fields in JSON format: + +- title: **Required.** String +- severity: **Required.** One of the "Critical", "High", "Medium", "Low", "Info" +- description: **Required.** String +- date: Date +- cwe: Int +- cve: String +- epss_score: Float +- epss_percentile: Float +- cvssv3: String +- cvssv3_score: Float +- cvssv4: String +- cvssv4_score: Float +- mitigation: String +- impact: String +- steps_to_reproduce: String +- severity_justification: String +- references: String +- active: Bool +- verified: Bool +- false_p: Bool +- out_of_scope: Bool +- risk_accepted: Bool +- under_review: Bool +- is_mitigated: Bool +- thread_id: String +- mitigated: Bool +- numerical_severity: Int +- param: String +- payload: String +- line: Int +- file_path: String +- component_name: String +- component_version: String +- static_finding: Bool +- dynamic_finding: Bool +- scanner_confidence: Int +- unique_id_from_tool: String +- vuln_id_from_tool: String +- sast_source_object: String +- sast_sink_object: String +- sast_source_line: Int +- sast_source_file_path: String +- nb_occurences: Int +- publish_date: Date +- service: String +- planned_remediation_date: Date +- planned_remediation_version: String +- effort_for_fixing: One of the "High", "Medium", "Low" +- tags: List of Strings +- kev_date: Date +- known_exploited: Bool +- ransomware_used: Bool +- fix_available: Bool + Example of JSON format: ```JSON @@ -39,13 +107,23 @@ Example of JSON format: "cve": "CVE-2020-36234", "cwe": 261, "cvssv3": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "cvssv4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N", + "cvssv4_score": 7.3, + "known_exploited": true, + "ransomware_used": true, + "fix_available": true, + "kev_date": "2024-05-01", "file_path": "src/first.cpp", "line": 13, "endpoints": [ { "host": "exemple.com" } - ] + ], + "tags": [ + "security", + "myTag" + ], }, { "title": "test title with endpoints as strings", @@ -144,9 +222,11 @@ Example: ``` ### Sample Scan Data + Sample Generic Findings Import scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/generic). ### Default Deduplication Hashcode Fields + By default, DefectDojo identifies duplicate Findings using these [hashcode fields](https://docs.defectdojo.com/en/working_with_findings/finding_deduplication/about_deduplication/): - title diff --git a/dojo/tools/generic/csv_parser.py b/dojo/tools/generic/csv_parser.py index f29f434f198..55d0845f0a0 100644 --- a/dojo/tools/generic/csv_parser.py +++ b/dojo/tools/generic/csv_parser.py @@ -81,6 +81,26 @@ def _get_findings_csv(self, filename): if len(cvss_objects) > 0: finding.cvssv3 = cvss_objects[0].clean_vector() + if "CVSSV4" in row: + cvss4_objects = cvss_parser.parse_cvss_from_text(row["CVSSV4"]) + if len(cvss4_objects) > 0: + finding.cvssv4 = cvss4_objects[0].clean_vector() + + if "CVSSV4_score" in row: + finding.cvssv4_score = float(row["CVSSV4_score"]) + + if "kev_date" in row: + finding.kev_date = parse(row["kev_date"]) + + if "known_exploited" in row: + finding.known_exploited = bool(row["known_exploited"]) + + if "ransomware_used" in row: + finding.ransomware_used = bool(row["ransomware_used"]) + + if "fix_available" in row: + finding.fix_available = bool(row["fix_available"]) + # manage endpoints if "Url" in row: finding.unsaved_endpoints = [ diff --git a/dojo/tools/generic/json_parser.py b/dojo/tools/generic/json_parser.py index 1a77bd0551e..b5de914d393 100644 --- a/dojo/tools/generic/json_parser.py +++ b/dojo/tools/generic/json_parser.py @@ -65,6 +65,8 @@ def _get_test_json(self, data): "epss_percentile", "cvssv3", "cvssv3_score", + "cvssv4", + "cvssv4_score", "mitigation", "impact", "steps_to_reproduce", @@ -102,6 +104,10 @@ def _get_test_json(self, data): "planned_remediation_version", "effort_for_fixing", "tags", + "kev_date", + "known_exploited", + "ransomware_used", + "fix_available", }.union(required) not_allowed = sorted(set(item).difference(allowed)) if not_allowed: diff --git a/unittests/scans/generic/generic_report_kev_cvssv4.csv b/unittests/scans/generic/generic_report_kev_cvssv4.csv new file mode 100644 index 00000000000..7b7054f137b --- /dev/null +++ b/unittests/scans/generic/generic_report_kev_cvssv4.csv @@ -0,0 +1,2 @@ +Date,Title,CweId,epss_score,epss_percentile,Url,Severity,Description,Mitigation,Impact,References,Active,Verified,FalsePositive,Duplicate,CVSSV4,CVSSV4_score,known_exploited,ransomware_used,fix_available,kev_date,CVSSV3 +01/30/2018,"Test finding",0,.00042,.23474,https://192.168.1.1/,Low,"Test finding description",,,,False,False,False,False,"CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N","7.3",True,True,True,"09/11/2025","CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N"" diff --git a/unittests/scans/generic/generic_report_kev_cvssv4.json b/unittests/scans/generic/generic_report_kev_cvssv4.json new file mode 100644 index 00000000000..ee75bc67508 --- /dev/null +++ b/unittests/scans/generic/generic_report_kev_cvssv4.json @@ -0,0 +1,46 @@ +{ + "findings": [ + { + "title": "test title", + "description": "Some very long description with\n\n some UTF-8 chars à qu'il est beau", + "active": true, + "verified": true, + "severity": "Medium", + "impact": "Some impact", + "date": "2021-01-06", + "cve": "CVE-2020-36234", + "cwe": 261, + "cvssv3": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "tags": [ + "security", + "network" + ], + "unique_id_from_tool": "3287f2d0-554f-491b-8516-3c349ead8ee5", + "vuln_id_from_tool": "TEST1", + "known_exploited": true, + "ransomware_used": true, + "fix_available": true, + "kev_date": "2024-05-01", + "cvssv4": "CVSS:4.0/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "cvssv4_score": 7.3 + }, + { + "title": "test title2", + "description": "Some very long description with\n\n some UTF-8 chars à qu'il est beau2", + "active": true, + "verified": false, + "severity": "Medium", + "impact": "Some impact", + "date": "2021-01-06", + "cve": "CVE-2020-36235", + "cwe": 287, + "cvssv3": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "tags": [ + "security", + "network" + ], + "unique_id_from_tool": "42500af3-68c5-4dc3-8022-191d93c2f1f7", + "vuln_id_from_tool": "TEST2" + } + ] +} \ No newline at end of file From 9acaab2d2d140f76aef49c6eec66e023e03c89b4 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Fri, 12 Sep 2025 13:02:28 -0600 Subject: [PATCH 13/16] Github Vulnerability Parser: Update docs to generate correct schema The docs became outdated from the parser --- .../parsers/file/github_vulnerability.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/en/connecting_your_tools/parsers/file/github_vulnerability.md b/docs/content/en/connecting_your_tools/parsers/file/github_vulnerability.md index 71350815e95..4d92f546685 100644 --- a/docs/content/en/connecting_your_tools/parsers/file/github_vulnerability.md +++ b/docs/content/en/connecting_your_tools/parsers/file/github_vulnerability.md @@ -183,8 +183,8 @@ def get_dependabot_alerts_repository(repo, owner): ) result = request.json() - output_result["data"]["repository"]["name"] = result["data"]["repository"][ - "name" + output_result["data"]["repository"]["nameWithOwner"] = result["data"]["repository"][ + "nameWithOwner" ] output_result["data"]["repository"]["url"] = result["data"]["repository"]["url"] if result["data"]["repository"]["vulnerabilityAlerts"]["totalCount"] == 0: From 19d708a16edd6ec5ce78a71157f138aa93943b47 Mon Sep 17 00:00:00 2001 From: Paul Osinski Date: Fri, 12 Sep 2025 15:06:09 -0400 Subject: [PATCH 14/16] update pro changelog 2.50.1 --- docs/content/en/changelog/changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/content/en/changelog/changelog.md b/docs/content/en/changelog/changelog.md index 03bffdbac42..712dcfa2cb4 100644 --- a/docs/content/en/changelog/changelog.md +++ b/docs/content/en/changelog/changelog.md @@ -10,6 +10,11 @@ For Open Source release notes, please see the [Releases page on GitHub](https:// ## Sept 2025: v2.50 +### Sept 9, 2025: v2.50.1 + +* **(Tools)** Removed CSV limit for Qualys HackerGuardian +* **(SSO)** Removed Force Password Reset for users created via SSO + ### Sept 2, 2025: v2.50.0 * **(Pro UI)** "Date During" filter has been added to the UI, allowing users to filter by a range of dates From 3ccf079cefe953974f734dde0100df0209ed12ac Mon Sep 17 00:00:00 2001 From: Valentijn Scholten Date: Fri, 12 Sep 2025 22:59:25 +0200 Subject: [PATCH 15/16] add None check --- dojo/jira_link/helper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dojo/jira_link/helper.py b/dojo/jira_link/helper.py index 00385e733e4..87dab3d291b 100644 --- a/dojo/jira_link/helper.py +++ b/dojo/jira_link/helper.py @@ -148,7 +148,9 @@ def is_keep_in_sync_with_jira(finding): jira_issue_exists = finding.has_jira_issue or (finding.finding_group and finding.finding_group.has_jira_issue) if jira_issue_exists: # Determine if any automatic sync should occur - keep_in_sync_enabled = get_jira_instance(finding).finding_jira_sync + jira_instance = get_jira_instance(finding) + if jira_instance: + keep_in_sync_enabled = jira_instance.finding_jira_sync return keep_in_sync_enabled From 790fdfab2f1f705af2e3fef64f37c9687d3711dc Mon Sep 17 00:00:00 2001 From: DefectDojo release bot Date: Mon, 15 Sep 2025 14:31:17 +0000 Subject: [PATCH 16/16] Update versions in application files --- components/package.json | 2 +- dojo/__init__.py | 2 +- helm/defectdojo/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/package.json b/components/package.json index d96e5e51133..c6880683761 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.51.0-dev", + "version": "2.50.2", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/dojo/__init__.py b/dojo/__init__.py index 82fe1512626..b094d840779 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.50.1" +__version__ = "2.50.2" __url__ = "https://github.com/DefectDojo/django-DefectDojo" __docs__ = "https://documentation.defectdojo.com" diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 8365c40bf5d..3931a9c6f43 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.51.0-dev" +appVersion: "2.50.2" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.207-dev +version: 1.6.207 icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap