Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
17f7d6b
Update versions in application files
Sep 2, 2025
7338c97
Merge pull request #13093 from DefectDojo/master-into-bugfix/2.50.0-2…
rossops Sep 2, 2025
a653695
Password Reset: Disable for SSO users (#13079)
Maffooch Sep 2, 2025
2a252ca
:tada: Add fix_available to KrakenDAudit (#13055)
manuel-sommer Sep 2, 2025
e02c26c
:tada: add VAR vulnid (#13096)
manuel-sommer Sep 4, 2025
cec647c
System settings: correct page title/breadcrumbs (#13083)
valentijnscholten Sep 4, 2025
97d6434
:tada: Add number of fix_available information to test view (#13109)
manuel-sommer Sep 4, 2025
44cbfec
:tada: Add fix_available information to aqua parser #12633 (#13106)
manuel-sommer Sep 4, 2025
de1f539
uwsgi: default to 4 processes x 4 threads (#13080)
valentijnscholten Sep 4, 2025
45030d5
allow qualys hacker guardian parser to parse larger csv files (#13120)
Jino-T Sep 5, 2025
8ec84b4
Update changelog 2.50 (#13121)
paulOsinski Sep 5, 2025
7eee818
semgrep pro parser (#12848)
valentijnscholten Sep 5, 2025
8e949da
:bug: Implement Wazuh v4.8 (#12739)
manuel-sommer Sep 5, 2025
7d3b999
:tada: Add fix_available information to jfrog (#13115)
manuel-sommer Sep 5, 2025
25e532e
:tada: Add fix_available information to jfrog xray unified parser #12…
manuel-sommer Sep 5, 2025
18330a9
ruff (#13122)
valentijnscholten Sep 5, 2025
5ada3e3
:bug: Fix finding_group view (#13119)
manuel-sommer Sep 5, 2025
aeb7c4a
fix default order to finding_groups
LeoOMaia Sep 6, 2025
433208a
Merge pull request #13127 from LeoOMaia/default-findinggroup
rossops Sep 8, 2025
f72cb99
Update versions in application files
Sep 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile.django-alpine
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ ENV \
DD_INITIALIZE=true \
DD_UWSGI_MODE="socket" \
DD_UWSGI_ENDPOINT="0.0.0.0:3031" \
DD_UWSGI_NUM_OF_PROCESSES="2" \
DD_UWSGI_NUM_OF_THREADS="2"
DD_UWSGI_NUM_OF_PROCESSES="4" \
DD_UWSGI_NUM_OF_THREADS="4"
ENTRYPOINT ["/entrypoint-uwsgi.sh"]

FROM django AS django-unittests
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.django-debian
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ ENV \
DD_INITIALIZE=true \
DD_UWSGI_MODE="socket" \
DD_UWSGI_ENDPOINT="0.0.0.0:3031" \
DD_UWSGI_NUM_OF_PROCESSES="2" \
DD_UWSGI_NUM_OF_THREADS="2"
DD_UWSGI_NUM_OF_PROCESSES="4" \
DD_UWSGI_NUM_OF_THREADS="4"
ENTRYPOINT ["/entrypoint-uwsgi.sh"]

FROM django AS django-unittests
Expand Down
2 changes: 1 addition & 1 deletion components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "defectdojo",
"version": "2.50.0",
"version": "2.50.1",
"license" : "BSD-3-Clause",
"private": true,
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions docker/entrypoint-uwsgi-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ exec uwsgi \
--protocol uwsgi \
--wsgi dojo.wsgi:application \
--enable-threads \
--processes "${DD_UWSGI_NUM_OF_PROCESSES:-2}" \
--threads "${DD_UWSGI_NUM_OF_THREADS:-2}" \
--processes "${DD_UWSGI_NUM_OF_PROCESSES:-4}" \
--threads "${DD_UWSGI_NUM_OF_THREADS:-4}" \
--reload-mercy 1 \
--worker-reload-mercy 1 \
--py-autoreload 1 \
Expand Down
4 changes: 2 additions & 2 deletions docker/entrypoint-uwsgi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ exec uwsgi \
"--${DD_UWSGI_MODE}" "${DD_UWSGI_ENDPOINT}" \
--protocol uwsgi \
--enable-threads \
--processes "${DD_UWSGI_NUM_OF_PROCESSES:-2}" \
--threads "${DD_UWSGI_NUM_OF_THREADS:-2}" \
--processes "${DD_UWSGI_NUM_OF_PROCESSES:-4}" \
--threads "${DD_UWSGI_NUM_OF_THREADS:-4}" \
--wsgi dojo.wsgi:application \
--buffer-size="${DD_UWSGI_BUFFER_SIZE:-8192}" \
--http 0.0.0.0:8081 --http-to "${DD_UWSGI_ENDPOINT}" \
Expand Down
8 changes: 8 additions & 0 deletions docs/content/en/changelog/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ Here are the release notes for **DefectDojo Pro (Cloud Version)**. These release

For Open Source release notes, please see the [Releases page on GitHub](https://github.com/DefectDojo/django-DefectDojo/releases), or alternatively consult the Open Source [upgrade notes](/en/open_source/upgrading/upgrading_guide/).

## Sept 2025: v2.50

### 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
* **(Pro UI)** Vulnerability ID column can now be sorted, however the sorting only considers the **first** vulnerability ID.
* **(Pro UI)** Request/Response pairs can now be added / updated / deleted via the Edit Finding form.

## August 2025: v2.49

The Pro UI has been significantly reorganized, with changes to page organization.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: "Semgrep Pro JSON Report"
toc_hide: true
---
Import Semgrep Pro findings in JSON format.

### Sample Scan Data
Sample Semgrep Pro JSON Report scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/semgrep_pro).

### Default Deduplication
By default, DefectDojo uses the `match_based_id` from Semgrep Pro for deduplication. If this is not available, it falls back to using a combination of:
- title
- file path
- line number

### Fields Mapped
The following fields are mapped from the Semgrep Pro JSON report:

#### Basic Information
- title: Mapped from `rule_name`
- severity: Mapped from Semgrep Pro severity levels (ERROR/HIGH → High, WARNING/MEDIUM → Medium, INFO/LOW → Low)
- file_path: Path to the affected file from `location.file_path`
- line: Line number from `location.line`
- unique_id_from_tool: Mapped from `match_based_id`

#### Status Fields
- active: Set to false if status is "fixed" or "removed"
- verified: Set to true if triage_state is not "untriaged"

#### Rich Content Fields
- description: Includes:
- Rule message and details
- CWE references
- OWASP references
- Categories
- Triage information
- impact: Includes:
- Vulnerability classes
- Confidence level
- Repository information
- mitigation: Includes:
- Guidance summary
- Detailed instructions
- Auto-fix suggestions
- Auto-triage information
- Component details and risk level
- references: Includes:
- Line of code URL
- CWE references
- OWASP references
- External ticket information

#### Component Information
- component_name: Mapped from `assistant.component.tag`

#### Additional Fields
- static_finding: Always set to true
- dynamic_finding: Always set to false
- cwe: Extracted from first CWE reference if available
- date: Mapped from `created_at`
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ handle 4 concurrent connections.
Based on your resource settings, you can tweak:

- `DD_UWSGI_NUM_OF_PROCESSES` for the number of spawned processes.
(default 2)
(default 4)
- `DD_UWSGI_NUM_OF_THREADS` for the number of threads in these
processes. (default 2)
processes. (default 4)

For example, you may have 4 processes with 6 threads each, yielding 24
concurrent connections.
Expand Down
39 changes: 0 additions & 39 deletions docs/content/en/open_source/performance.md

This file was deleted.

2 changes: 1 addition & 1 deletion dojo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.0"
__version__ = "2.50.1"
__url__ = "https://github.com/DefectDojo/django-DefectDojo"
__docs__ = "https://documentation.defectdojo.com"
7 changes: 7 additions & 0 deletions dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,13 @@ class Meta:
model = UserContactInfo
fields = "__all__"

def validate(self, data):
user = data.get("user", None) or self.instance.user
if data.get("force_password_reset", False) and not user.has_usable_password():
msg = "Password resets are not allowed for users authorized through SSO."
raise ValidationError(msg)
return super().validate(data)


class UserStubSerializer(serializers.ModelSerializer):
class Meta:
Expand Down
4 changes: 2 additions & 2 deletions dojo/finding_group/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ def order_field(self, request: HttpRequest, group_findings_queryset: QuerySet[Fi
order_field_param = order_field_param[1:] if reverse_order else order_field_param
if order_field_param in {"name", "creator", "findings_count", "sla_deadline"}:
prefix = "-" if reverse_order else ""
group_findings_queryset = group_findings_queryset.order_by(f"{prefix}{order_field_param}")
return group_findings_queryset
return group_findings_queryset.order_by(f"{prefix}{order_field_param}")
return group_findings_queryset.order_by("id")

def filters(self, request: HttpRequest) -> tuple[str, str | None, list[str], list[str]]:
name_filter: str = request.GET.get("name", "").lower()
Expand Down
7 changes: 7 additions & 0 deletions dojo/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2388,7 +2388,14 @@ class Meta:
exclude = ["user", "slack_user_id"]

def __init__(self, *args, **kwargs):
user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)
# Do not expose force password reset if the current user does not have a password to reset
if user is not None:
if not user.has_usable_password():
self.fields["force_password_reset"].disabled = True
self.fields["force_password_reset"].help_text = "This user is authorized through SSO, and does not have a password to reset"
# Determine some other settings based on the current user
current_user = get_current_user()
if not current_user.is_superuser:
if not user_has_configuration_permission(current_user, "auth.change_user") and \
Expand Down
1 change: 1 addition & 0 deletions dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1876,6 +1876,7 @@ def saml2_attrib_map_format(din):
"TS-": """https://tailscale.com/security-bulletins#""", # e.g. https://tailscale.com/security-bulletins or https://tailscale.com/security-bulletins#ts-2022-001-1243
"TYPO3-": "https://typo3.org/security/advisory/", # e.g. https://typo3.org/security/advisory/typo3-core-sa-2025-010
"USN-": "https://ubuntu.com/security/notices/", # e.g. https://ubuntu.com/security/notices/USN-6642-1
"VAR-": "https://cvepremium.circl.lu/vuln/", # e.g. https://cvepremium.circl.lu/vuln/var-201801-0152
"VNS": "https://vulners.com/",
"WID-SEC-W-": "https://cvepremium.circl.lu/vuln/", # e.g. https://cvepremium.circl.lu/vuln/wid-sec-w-2025-1468
}
Expand Down
4 changes: 2 additions & 2 deletions dojo/system_settings/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def get(
# Set up the initial context
context = self.get_context(request)
# Add some breadcrumbs
add_breadcrumb(title="Application settings", top_level=False, request=request)
add_breadcrumb(title="System settings", top_level=False, request=request)
# Render the page
return render(request, self.get_template(), context)

Expand All @@ -142,6 +142,6 @@ def post(
# Check the status of celery
request, _ = self.validate_form(request, context)
# Add some breadcrumbs
add_breadcrumb(title="Application settings", top_level=False, request=request)
add_breadcrumb(title="System settings", top_level=False, request=request)
# Render the page
return render(request, self.get_template(), context)
2 changes: 2 additions & 0 deletions dojo/templates/dojo/view_test.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ <h3 class="pull-left">
<th>{% trans "Dates" %}</th>
<th>{% trans "Updated" %}</th>
{% if test.percent_complete > 0 %}<th>{% trans "Progress" %}</th>{% endif %}
<th>{% trans "Fix Available" %}</th>
<th>{% trans "Version" %}</th>
{% if 'TRACK_IMPORT_HISTORY'|setting_enabled and test.test_import_set.all %}
<th>
Expand Down Expand Up @@ -158,6 +159,7 @@ <h3 class="pull-left">
</div>
</td>
{% endif %}
<td>{{ fix_available_count }}</td>
<td>{{ test.version }}</td>
{% if 'TRACK_IMPORT_HISTORY'|setting_enabled and test.total_reimport_count %}
<td class="text-right">{{ test.total_reimport_count }}</td>
Expand Down
2 changes: 2 additions & 0 deletions dojo/test/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,12 @@ def get_findings(self, request: HttpRequest, test: Test):
finding_filter_class = FindingFilterWithoutObjectLookups if filter_string_matching else FindingFilter
findings = finding_filter_class(request.GET, pid=test.engagement.product.id, queryset=findings)
paged_findings = get_page_items_and_count(request, prefetch_for_findings(findings.qs), 25, prefix="findings")
fix_available_count = findings.qs.filter(fix_available=True).count()

return {
"findings": paged_findings,
"filtered": findings,
"fix_available_count": fix_available_count,
}

def get_note_form(self, request: HttpRequest):
Expand Down
12 changes: 10 additions & 2 deletions dojo/tools/aqua/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ def get_item(self, resource, vuln, test):
resource_name = resource.get("name", resource.get("path"))
resource_version = resource.get("version", "No version")
vulnerability_id = vuln.get("name", "No CVE")
fix_version = vuln.get("fix_version", "None")
fix_available = False
fix_version = vuln.get("fix_version", None)
if fix_version is not None:
fix_available = True
description = vuln.get("description", "No description.") + "\n"
if resource.get("path"):
description += "**Path:** " + resource.get("path") + "\n"
Expand Down Expand Up @@ -222,6 +225,7 @@ def get_item(self, resource, vuln, test):
component_name=resource.get("name"),
component_version=resource.get("version"),
impact=severity,
fix_available=fix_available,
)

cvss_data = parse_cvss_data(cvssv3)
Expand All @@ -244,7 +248,10 @@ def get_item_v2(self, item, test):
severity = self.severity_of(float(item["score"]))
description = item.get("description")
solution = item.get("solution")
fix_version = item.get("fix_version")
fix_available = False
fix_version = item.get("fix_version", None)
if fix_version is not None:
fix_available = True
if solution:
mitigation = solution
elif fix_version:
Expand All @@ -260,6 +267,7 @@ def get_item_v2(self, item, test):
severity=severity,
impact=severity,
mitigation=mitigation,
fix_available=fix_available,
)
finding.unsaved_vulnerability_ids = [vulnerability_id]
return finding
Expand Down
3 changes: 3 additions & 0 deletions dojo/tools/jfrog_xray_unified/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ def get_item(vulnerability, test):
if "cvss_v2_vector" in worstCve:
cvss_v2 = worstCve["cvss_v2_vector"]

fix_available = False
if (
"fixed_versions" in vulnerability
and len(vulnerability["fixed_versions"]) > 0
):
mitigation = "Versions containing a fix:\n"
mitigation += "\n".join(vulnerability["fixed_versions"])
fix_available = True

if (
"external_advisory_source" in vulnerability
Expand Down Expand Up @@ -138,6 +140,7 @@ def get_item(vulnerability, test):
date=scan_time,
unique_id_from_tool=vulnerability["issue_id"],
tags=tags,
fix_available=fix_available,
)

cvss_data = parse_cvss_data(cvssv3)
Expand Down
3 changes: 3 additions & 0 deletions dojo/tools/jfrogxray/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def get_item(vulnerability, test):
cvssv3 = None
cvss_v3 = "No CVSS v3 score."
mitigation = None
fix_available = False
extra_desc = ""
# Some entries have no CVE entries, despite they exist. Example
# CVE-2017-1000502.
Expand All @@ -95,6 +96,7 @@ def get_item(vulnerability, test):
mitigation += "\n".join(
vulnerability["component_versions"]["fixed_versions"],
)
fix_available = True

if "vulnerable_versions" in vulnerability["component_versions"]:
extra_desc = "\n**Versions that are vulnerable:**\n"
Expand Down Expand Up @@ -160,6 +162,7 @@ def get_item(vulnerability, test):
static_finding=True,
dynamic_finding=False,
cvssv3=cvssv3,
fix_available=fix_available,
)
if vulnerability_ids:
finding.unsaved_vulnerability_ids = vulnerability_ids
Expand Down
1 change: 1 addition & 0 deletions dojo/tools/krakend_audit/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def get_findings(self, file, test):
mitigation=message,
static_finding=True,
dynamic_finding=False,
fix_available=True,
)
findings.append(finding)
return findings
2 changes: 2 additions & 0 deletions dojo/tools/qualys_hacker_guardian/parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import csv
import io
import sys

from dateutil import parser as date_parser

Expand Down Expand Up @@ -88,6 +89,7 @@ def get_findings(self, filename, test):
content = filename.read()
if isinstance(content, bytes):
content = content.decode("utf-8")
csv.field_size_limit(int(sys.maxsize / 10))
reader = csv.DictReader(io.StringIO(content), delimiter=",", quotechar='"')
dupes = {}
for row in reader:
Expand Down
Empty file.
Loading