Skip to content

Commit a86218e

Browse files
authored
Merge branch 'dev' into static-group
2 parents 8ccf82c + dae9299 commit a86218e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1145
-256
lines changed

.github/workflows/close-stale.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Close Stale Issues and PRs
2+
3+
on:
4+
schedule:
5+
# Run daily at 02:00 UTC
6+
- cron: '0 2 * * *'
7+
workflow_dispatch:
8+
# Allow manual triggering
9+
10+
permissions:
11+
issues: write
12+
pull-requests: write
13+
14+
jobs:
15+
close-stale:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Close stale issues and PRs
19+
uses: actions/stale@v9
20+
with:
21+
# Disable automatic stale marking - only close manually labeled items
22+
days-before-stale: -1
23+
days-before-close: 7
24+
stale-issue-label: 'stale'
25+
stale-pr-label: 'stale'
26+
close-issue-message: 'This issue has been automatically closed because it was manually labeled as stale. If you believe this was closed in error, please reopen it and remove the stale label.'
27+
close-pr-message: 'This PR has been automatically closed because it was manually labeled as stale. If you believe this was closed in error, please reopen it and remove the stale label.'

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,4 @@ docs/.devcontainer/devcontainer.json
147147
docs/.devcontainer/Dockerfile
148148
docs/LICENSE
149149
docs/.hugo_build.lock
150+
.cursor-rules

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ services:
120120
source: ./docker/extra_settings
121121
target: /app/docker/extra_settings
122122
postgres:
123-
image: postgres:17.5-alpine@sha256:ec01646a894c7c5aec8a29e0fe723ab093e24e2e055940090d282d8ab1c79d26
123+
image: postgres:17.5-alpine@sha256:6567bca8d7bc8c82c5922425a0baee57be8402df92bae5eacad5f01ae9544daa
124124
environment:
125125
POSTGRES_DB: ${DD_DATABASE_NAME:-defectdojo}
126126
POSTGRES_USER: ${DD_DATABASE_USER:-defectdojo}
@@ -129,7 +129,7 @@ services:
129129
- defectdojo_postgres:/var/lib/postgresql/data
130130
redis:
131131
# Pinning to this version due to licensing constraints
132-
image: redis:7.2.10-alpine@sha256:b49011d66bd28754587d44ac9079d6088086fd6063cb7c554b77a5c2b2cbec1c
132+
image: redis:7.2.10-alpine@sha256:395ccd7ee4db0867de0d0410f4712a9e0331cff9fdbd864f71ec0f7982d3ffe6
133133
volumes:
134134
- defectdojo_redis:/data
135135
volumes:

docs/content/en/about_defectdojo/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ There are two different methods to import a report from a security tool into Def
4848
- **Import** handles the report as a single point-in-time record. Importing a report creates a Test within DefectDojo that holds the Findings rendered from that report.
4949
- **Reimport** is used to extend an existing Test. If you have a more open-ended approach to your testing process, you continuously Reimport the latest version of your report to an existing Test. DefectDojo will compare the results of the incoming report to your existing data, record any changes, and then adjust the Findings in the Test so that they match the latest report.
5050

51-
Both methods also use **Deduplication** differently: while two discrete Imported Tests in the same Product will identify and label duplicate Findings, Reimport will discard duplicate Findings altogether.
51+
Both methods also use **Deduplication** differently: while two discrete Imported Tests in the same Product will identify and label duplicate Findings, Reimport will skip duplicates in uploaded reports as theses Findings already exist in Defect Dojo.
5252

5353
Generally speaking - if a point-in-time report is what you need, Import is the best method to use. If you are continuously running and ingesting reports from a tool, Reimport is the better method for keeping things organized.
5454

docs/content/en/open_source/archived_docs/usage/features.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -386,15 +386,12 @@ details about the deduplication process : switch
386386

387387
### Deduplication - APIv2 parameters
388388

389-
- `close_old_findings` : if true, findings that are not
390-
duplicates and that were in the previous scan of the same type
391-
(example ZAP) for the same engagement (or product in case of
392-
\"close_old_findings_product_scope\") and that are not present in the new
393-
scan are closed (Inactive, Verified, Mitigated).
394-
- `close_old_findings_product_scope` : if true, close_old_findings applies
395-
to all findings of the same type in the product. Note that
396-
\"Deduplication on engagement\" is no longer used to determine the
397-
scope of close_old_findings.
389+
| Parameter | Import behaviour | Reimport Behaviour |
390+
|-----------|------------------|-------------------|
391+
| `close_old_findings` | if `true`, findings that are not duplicates and that were in the previous scan of the same type (example ZAP) for the same **engagement** (or product in case of `close_old_findings_product_scope`) and that are not present in the new scan are closed (`Inactive`, `Verified`, `Mitigated`). | if `true`, findings that that are in the same **test** and that are not present in the new scan are closed (`Inactive`, `Verified`, `Mitigated`) |
392+
| `close_old_findings_product_scope` | if true, `close_old_findings` applies to all findings of the same type in the whole **product**. Note that "Deduplication on engagement" is no longer used to determine the scope of `close_old_findings` | has no effect |
393+
394+
The `close_old_findings` feature will respect the value of the `service` field to only close findings with an identical `service` value.
398395

399396
### Deduplication / Similar findings
400397

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: 'Upgrading to DefectDojo Version 2.48.2'
3+
toc_hide: true
4+
weight: -20250602
5+
description: Tag invalid character cleanup
6+
---
7+
8+
## Tag Formatting Update
9+
In [2.46.0](../2.46.md) tag validation was added to disallow commas, spaces and quotes in tags. Some parsers were still creating tags with invalid characters. This is fixed in this release and this release will run another data migration to replace any invalid character in tag with an underscore '`_`'.

dojo/api_v2/serializers.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,14 +2302,17 @@ class ImportScanSerializer(CommonImportScanSerializer):
23022302
close_old_findings = serializers.BooleanField(
23032303
required=False,
23042304
default=False,
2305-
help_text="Select if old findings no longer present in the report get closed as mitigated when importing. "
2306-
"If service has been set, only the findings for this service will be closed.",
2305+
help_text="Old findings no longer present in the new report get closed as mitigated when importing. "
2306+
"If service has been set, only the findings for this service will be closed. "
2307+
"This only affects findings within the same engagement.",
23072308
)
23082309
close_old_findings_product_scope = serializers.BooleanField(
23092310
required=False,
23102311
default=False,
2311-
help_text="Select if close_old_findings applies to all findings of the same type in the product. "
2312-
"By default, it is false meaning that only old findings of the same type in the engagement are in scope.",
2312+
help_text="Old findings no longer present in the new report get closed as mitigated when importing. "
2313+
"If service has been set, only the findings for this service will be closed. "
2314+
"This only affects findings within the same product."
2315+
"By default, it is false meaning that only old findings of the same type in the engagement are in scope.",
23132316
)
23142317
version = serializers.CharField(
23152318
required=False, help_text="Version that was scanned.",
@@ -2380,15 +2383,15 @@ class ReImportScanSerializer(CommonImportScanSerializer):
23802383
# also for ReImport.
23812384
close_old_findings = serializers.BooleanField(
23822385
required=False,
2383-
default=True,
2384-
help_text="Select if old findings no longer present in the report get closed as mitigated when importing.",
2386+
default=False,
2387+
help_text="Old findings no longer present in the new report get closed as mitigated when importing. "
2388+
"If service has been set, only the findings for this service will be closed. "
2389+
"This only affects findings within the same test.",
23852390
)
23862391
close_old_findings_product_scope = serializers.BooleanField(
23872392
required=False,
23882393
default=False,
2389-
help_text="Select if close_old_findings applies to all findings of the same type in the product. "
2390-
"By default, it is false meaning that only old findings of the same type in the engagement are in scope. "
2391-
"Note that this only applies on the first call to reimport-scan.",
2394+
help_text="This has no effect on reimport",
23922395
)
23932396
version = serializers.CharField(
23942397
required=False,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 5.1.8 on 2025-07-17 20:45
2+
3+
import django.core.validators
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('dojo', '0233_remove_test_actual_time_remove_test_estimated_time'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='system_settings',
16+
name='maximum_password_length',
17+
field=models.IntegerField(default=48, help_text='Requires user to set passwords less than maximum length.', validators=[django.core.validators.MinValueValidator(9), django.core.validators.MaxValueValidator(48)], verbose_name='Maximum password length'),
18+
),
19+
migrations.AlterField(
20+
model_name='system_settings',
21+
name='minimum_password_length',
22+
field=models.IntegerField(default=9, help_text='Requires user to set passwords greater than minimum length.', validators=[django.core.validators.MinValueValidator(9), django.core.validators.MaxValueValidator(48)], verbose_name='Minimum password length'),
23+
),
24+
]

dojo/db_migrations/0235_alter_system_settings_time_zone_zoneinfo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class Migration(migrations.Migration):
88

99
dependencies = [
10-
('dojo', '0234_finding_cvssv4_finding_cvssv4_score'),
10+
('dojo', '0234_alter_system_settings_maximum_password_length_and_more'),
1111
]
1212

1313
operations = [

dojo/db_migrations/0236_clean_tags.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Generated by Django 5.0.8 on 2024-09-12 18:22
2+
3+
import logging
4+
from django.db import migrations
5+
from django.db.models import Q
6+
7+
logger = logging.getLogger(__name__)
8+
9+
# Only apply the process to models that _could_ have tags
10+
model_names = [
11+
"Product",
12+
"Endpoint",
13+
"Engagement",
14+
"Test",
15+
"Finding",
16+
"Finding_Template",
17+
"App_Analysis",
18+
"Objects_Product",
19+
]
20+
21+
22+
def clean_tag_value(tag: str) -> str:
23+
"""
24+
Clean each tag value by:
25+
- Converting all commas to hyphens
26+
- Converting all spaces to underscores
27+
- Removing all single/double quotes
28+
"""
29+
return tag.replace(",", "-").replace(" ", "_").replace('"', "").replace("'", "")
30+
31+
32+
def clean_all_tag_fields(apps, schema_editor):
33+
"""
34+
Cleans tag values for all models in the `model_names` list, removing unwanted characters.
35+
Updates both 'tags' and 'inherited_tags' fields where applicable.
36+
"""
37+
updated_count = {}
38+
for model_name in model_names:
39+
TaggedModel = apps.get_model("dojo", model_name)
40+
unique_tags_per_model = {}
41+
count_per_model = 0
42+
# Only fetch the objects with tags that contain a character in violation
43+
queryset = (
44+
TaggedModel.objects.filter(
45+
Q(**{"tags__name__icontains": ","})
46+
| Q(**{"tags__name__icontains": " "})
47+
| Q(**{"tags__name__icontains": '"'})
48+
| Q(**{"tags__name__icontains": "'"})
49+
)
50+
.distinct()
51+
.prefetch_related("tags")
52+
)
53+
# Iterate over each instance to clean the tags. The iterator is used here
54+
# to prevent loading the entire queryset into memory at once. Instead, we
55+
# will only process 500 objects at a time
56+
for instance in queryset.iterator(chunk_size=500):
57+
# Get the current list of tags to work with
58+
raw_tags = instance.tags.all()
59+
# Clean each tag here while preserving the original value
60+
cleaned_tags = {tag.name: clean_tag_value(tag.name) for tag in raw_tags}
61+
# Quick check to avoid writing things without impact
62+
if cleaned_tags:
63+
instance.tags.set(list(cleaned_tags.values()), clear=True)
64+
count_per_model += 1
65+
# Update the running list of cleaned tags with the changes on this model
66+
unique_tags_per_model.update(cleaned_tags)
67+
# Add a quick logging statement every 100 objects cleaned
68+
if count_per_model > 0 and count_per_model % 100 == 0:
69+
logger.info(
70+
f"{TaggedModel.__name__}.tags: cleaned {count_per_model} tags..."
71+
)
72+
# Update the final count of the tags cleaned for the given model
73+
if count_per_model:
74+
updated_count[f"{TaggedModel.__name__}"] = (
75+
count_per_model,
76+
unique_tags_per_model,
77+
)
78+
"""
79+
Write a helpful statement about what tags were changed for each model in the list.
80+
It looks something like this:
81+
82+
Product: 1 instances cleaned
83+
"quoted string with spaces" -> quoted_string_with_spaces
84+
"quoted with spaces, and also commas!" -> quoted_with_spaces-_and_also_commas!
85+
"quoted,comma,tag" -> quoted-comma-tag
86+
Engagement: 1 instances cleaned
87+
"quoted string with spaces" -> quoted_string_with_spaces
88+
"quoted with spaces, and also commas!" -> quoted_with_spaces-_and_also_commas!
89+
"quoted,comma,tag" -> quoted-comma-tag
90+
Test: 1 instances cleaned
91+
"quoted string with spaces" -> quoted_string_with_spaces
92+
"quoted with spaces, and also commas!" -> quoted_with_spaces-_and_also_commas!
93+
"quoted,comma,tag" -> quoted-comma-tag
94+
Finding: 1 instances cleaned
95+
"quoted string with spaces" -> quoted_string_with_spaces
96+
"quoted with spaces, and also commas!" -> quoted_with_spaces-_and_also_commas!
97+
"quoted,comma,tag" -> quoted-comma-tag
98+
"""
99+
for key, (count, tags) in updated_count.items():
100+
logger.info(f"{key}: {count} instances cleaned")
101+
for old, new in tags.items():
102+
if old != new:
103+
logger.info(f" {old} -> {new}")
104+
105+
106+
def cannot_turn_back_time(apps, schema_editor):
107+
"""
108+
We cannot possibly return to the original state without knowing
109+
the original value at the time the migration is revoked. Instead
110+
we will do nothing.
111+
"""
112+
pass
113+
114+
115+
class Migration(migrations.Migration):
116+
dependencies = [
117+
('dojo', '0235_alter_system_settings_time_zone_zoneinfo'),
118+
]
119+
120+
operations = [
121+
migrations.RunPython(clean_all_tag_fields, cannot_turn_back_time),
122+
]
123+

0 commit comments

Comments
 (0)