Skip to content

API: prevent duplicate saves of taggable entities or when pushing to JIRA#12607

Merged
mtesauro merged 17 commits into
DefectDojo:devfrom
valentijnscholten:api-duplicate-saves-tags-jira-fix
Jun 19, 2025
Merged

API: prevent duplicate saves of taggable entities or when pushing to JIRA#12607
mtesauro merged 17 commits into
DefectDojo:devfrom
valentijnscholten:api-duplicate-saves-tags-jira-fix

Conversation

@valentijnscholten

@valentijnscholten valentijnscholten commented Jun 15, 2025

Copy link
Copy Markdown
Member

When creating or updating findings (or other entities that have a tags field), the Finding.save() method was called multiple times. Ultimately the finding was saved correctly, but this lead to unnecessary use of resources and could in the future lead to problems if/when the Defect Dojo could be relying more on Django signals such as post_save().

The above was caused by the way the tags fields were implemented long ago. I tried to tweak the TaggitSerializer to prevent the duplicate saves from happening, but this lead nowhere. Then I realized I could just tweak our TagListSerializer to make it work without the "extra" TaggitSerialiser.

I also looked at the from tagulous.contrib.drf import TagSerializer, but that doesn't support the single string with multiple comma separated tags that we re-added support for in #12434

Duplicate saves could also be triggered when the finding needed to be pushed to JIRA. This PR also resolves that.

Added some unit tests to make sure all looks well.

I had to rerecord one JIRA related tests around finding groups. Usually this is a red flag, but looking at the test case and asserts I think all is still good. Maybe the old codebase did some duplicagte/unnecessary/idempotent calls to JIRA?

[sc-10686]

@valentijnscholten valentijnscholten force-pushed the api-duplicate-saves-tags-jira-fix branch from 889933c to 609f903 Compare June 15, 2025 13:40
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions Bot added conflicts-detected docker New Migration Adding a new migration file. Take care when merging. settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR docs parser helm lint labels Jun 15, 2025
@valentijnscholten valentijnscholten changed the base branch from bugfix to dev June 15, 2025 13:40
@valentijnscholten valentijnscholten added this to the 2.48.0 milestone Jun 15, 2025
@valentijnscholten valentijnscholten changed the title prevent duplicate saves API: prevent duplicate saves of taggable entities Jun 15, 2025
@valentijnscholten valentijnscholten marked this pull request as ready for review June 15, 2025 15:18
@dryrunsecurity

dryrunsecurity Bot commented Jun 15, 2025

Copy link
Copy Markdown

DryRun Security

🔴 Risk threshold exceeded.

This pull request contains multiple security vulnerabilities including debug information exposure, verbose error logging that could aid attackers, and a potential CVE ID manipulation risk in different parts of the application's codebase.

Debug Information Exposure in dojo/jira_link/helper.py
Vulnerability Debug Information Exposure
Description Debug log messages directly log object contents without sanitization, potentially exposing sensitive internal details if debug logging is enabled in production.

raise ValueError(msg)
if isinstance(obj, Finding):
if obj.has_finding_group:
logger.debug("pushing finding group for %s to JIRA", obj)
return push_finding_group_to_jira(obj.finding_group, *args, **kwargs)
return push_finding_to_jira(obj, *args, **kwargs)
if isinstance(obj, Finding_Group):

Verbose Error Logging in dojo/api_v2/views.py
Vulnerability Verbose Error Logging
Description Error messages include the full list of tags and specific invalid tag, which could aid an attacker in enumerating valid tags through targeted error probing.

all_tags = serializers.TagSerializer({"tags": all_tags}).data[
"tags"
]
for tag in new_tags.validated_data["tags"]:
for sub_tag in tagulous.utils.parse_tags(tag):
if sub_tag not in all_tags:
all_tags.append(sub_tag)
new_tags = tagulous.utils.render_tags(all_tags)
finding.tags = new_tags
finding.save()
else:

CVE ID Manipulation in dojo/api_v2/serializers.py
Vulnerability CVE ID Manipulation
Description The code sets the CVE field by taking the first vulnerability ID from a user-provided list without validation. This could allow an attacker to manipulate the CVE identifier for a finding by providing a crafted list of vulnerability IDs.

# Overriding this to push add Push to JIRA functionality
def update(self, instance, validated_data):
# push_all_issues already checked in api views.py
push_to_jira = validated_data.pop("push_to_jira")
# Save vulnerability ids and pop them
parsed_vulnerability_ids = []
if (vulnerability_ids := validated_data.pop("vulnerability_id_set", None)):
logger.debug("VULNERABILITY_ID_SET: %s", vulnerability_ids)
parsed_vulnerability_ids.extend(vulnerability_id["vulnerability_id"] for vulnerability_id in vulnerability_ids)
logger.debug("SETTING CVE FROM VULNERABILITY_ID_SET: %s", parsed_vulnerability_ids[0])
validated_data["cve"] = parsed_vulnerability_ids[0]
# Save the reporter on the finding
if reporter_id := validated_data.get("reporter"):
instance.reporter = reporter_id
instance = super().update(
instance, validated_data,
)
if parsed_vulnerability_ids:
save_vulnerability_ids(instance, parsed_vulnerability_ids)
if push_to_jira:
jira_helper.push_to_jira(instance)
return instance
def validate(self, data):
if self.context["request"].method == "PATCH":

We've notified @mtesauro.


All finding details can be found in the DryRun Security Dashboard.

@valentijnscholten valentijnscholten changed the title API: prevent duplicate saves of taggable entities API: prevent duplicate saves of taggable entities or when pushing to JIRA Jun 15, 2025
@github-actions github-actions Bot removed docker New Migration Adding a new migration file. Take care when merging. settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR docs parser helm lint labels Jun 15, 2025
@github-actions

Copy link
Copy Markdown
Contributor

Conflicts have been resolved. A maintainer will review the pull request shortly.

@valentijnscholten valentijnscholten marked this pull request as draft June 16, 2025 06:50

@Maffooch Maffooch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really promising! Excellent work here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been manually adding this back in a pinch for so long, but have always forgot to commit it 🥴 thank you for doing so!

Comment on lines +1889 to +1890
# can we avoid this extra save? the cve has already been set above in validated_data. but there are no tests for this
# on finding update nothing is done # with vulnerability_ids?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the extra save could be removed since th vulnerability IDs have. dedicated function now

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that extra functions doesn't actually save anything on the finding itself, I think we have to add a small test to ensure a CVE is stored on the finding itself. Unless that field is slated for deprecation?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some tests to make sure vulnerability ids are stored and the cve is populated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please clean up those extra comments when you are done with all the manual testing/validation

Comment thread dojo/api_v2/views.py
"tags"
]
for tag in new_tags.validated_data["tags"]:
for sub_tag in tagulous.utils.parse_tags(tag):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious why this was needed. Does the tagulous.utils.parse_tags function not work as expected when passed a list vs individual strings?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC it parses the string repesentation of the list ending up with something like:

  • ["tag1"
  • "tag2"
  • "tag3"]
    But it could be that it was needed because I also changed the TagListSerializerField to not render the tags as one string. That changes allows us to just let the ModelSerializer serialize the tags without having to pop them and process them ourselves.

Comment thread dojo/models.py Outdated
valentijnscholten and others added 4 commits June 16, 2025 17:23
@valentijnscholten valentijnscholten marked this pull request as ready for review June 16, 2025 16:46

@Maffooch Maffooch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work here! These are my favorite kinds of changes to see here 🤗

@Maffooch Maffooch requested review from dogboat and hblankenship June 17, 2025 17:10

@mtesauro mtesauro left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

@mtesauro mtesauro merged commit 7cf5b09 into DefectDojo:dev Jun 19, 2025
78 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants