fix(tags): order single-finding delete tag-count decrements so concurrent deletes cannot deadlock - #15664
fix(tags): order single-finding delete tag-count decrements so concurrent deletes cannot deadlock#15664Maffooch wants to merge 2 commits into
Conversation
A single-object finding delete (Finding.delete(), used by
DELETE /api/v2/findings/{id}/) removed its tags through tagulous's
per-object clear(), which issues one tag-count UPDATE per tag in the
manager's own order rather than ascending tag-id order. Because each
UPDATE takes a row lock, two concurrent single-finding deletes touching
an overlapping tag set could acquire those tag-row locks in opposite
orders and deadlock (Postgres 40P01, "while updating tuple ... in
relation dojo_tagulous_finding_tags").
Route the delete through bulk_remove_all_tags -- the same ascending
tag-id ordering already used by the bulk cascade delete (#15486) and the
import add path (#15652) -- so every tag-count mutation shares one lock
order and the cycle cannot form. Clearing the through rows first also
leaves the tagulous pre_delete handler nothing to decrement, so counts
are not double-processed. No schema change / no migration.
Adds FindingDeleteTagLockOrderTest asserting the decrements are issued in
ascending tag-id order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i5bbWnQg2wKmQpvD7DRLz
|
This pull request contains multiple critical findings where the non-allowed author 'claude' modified numerous sensitive codepaths across the dojo application. These unauthorized changes affect key components including models, API endpoints, authorization logic, and utilities.
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in
|
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/finding/models.py' matches configured sensitive codepath pattern 'dojo/finding/*.py' and was modified by 'claude' (commit 3a48e0c) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/api_v2/prefetch/prefetcher.py (drs_7ba11ca9)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/api_v2/prefetch/prefetcher.py' matches configured sensitive codepath pattern 'dojo/api_v2/**/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/api_v2/serializers.py (drs_bee7b511)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/api_v2/serializers.py' matches configured sensitive codepath pattern 'dojo/api_v2/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/api_v2/views.py (drs_4181691f)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/api_v2/views.py' matches configured sensitive codepath pattern 'dojo/api_v2/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/authorization/api_permissions.py (drs_57153cc3)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/authorization/api_permissions.py' matches configured sensitive codepath pattern 'dojo/authorization/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/authorization/serializer_guards.py (drs_ecd35a55)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/authorization/serializer_guards.py' matches configured sensitive codepath pattern 'dojo/authorization/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/authorization/url_permissions.py (drs_6bacdd7c)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/authorization/url_permissions.py' matches configured sensitive codepath pattern 'dojo/authorization/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/context_processors.py (drs_7c906c77)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/context_processors.py' matches configured sensitive codepath pattern 'dojo/context_processors.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/decorators.py (drs_8d626b75)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/decorators.py' matches configured sensitive codepath pattern 'dojo/decorators.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/endpoint/models.py (drs_03fb1f24)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/endpoint/models.py' matches configured sensitive codepath pattern 'dojo/endpoint/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/engagement/services.py (drs_8f882b8b)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/engagement/services.py' matches configured sensitive codepath pattern 'dojo/engagement/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/filters.py (drs_e28be58d)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/filters.py' matches configured sensitive codepath pattern 'dojo/filters.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/finding/deduplication.py (drs_d13b3f87)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/finding/deduplication.py' matches configured sensitive codepath pattern 'dojo/finding/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/finding/helper.py (drs_8ffadc63)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/finding/helper.py' matches configured sensitive codepath pattern 'dojo/finding/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/finding/queries.py (drs_db31f889)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/finding/queries.py' matches configured sensitive codepath pattern 'dojo/finding/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/forms.py (drs_5e6ec98a)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/forms.py' matches configured sensitive codepath pattern 'dojo/forms.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/importers/auto_create_context.py (drs_e0251b24)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/importers/auto_create_context.py' matches configured sensitive codepath pattern 'dojo/importers/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/importers/base_location_manager.py (drs_fa81a6af)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/importers/base_location_manager.py' matches configured sensitive codepath pattern 'dojo/importers/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/jira/helper.py (drs_3537625b)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/jira/helper.py' matches configured sensitive codepath pattern 'dojo/jira/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/reports/queries.py (drs_52b58523)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/reports/queries.py' matches configured sensitive codepath pattern 'dojo/reports/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/reports/widgets.py (drs_ea5765df)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/reports/widgets.py' matches configured sensitive codepath pattern 'dojo/reports/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/search/views.py (drs_0abdb16b)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/search/views.py' matches configured sensitive codepath pattern 'dojo/search/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/templatetags/display_tags.py (drs_70e8d905)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/templatetags/display_tags.py' matches configured sensitive codepath pattern 'dojo/templatetags/*.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/urls.py (drs_623ea93c)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/urls.py' matches configured sensitive codepath pattern 'dojo/urls.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
🔴 Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/utils.py (drs_da9a2c08)
| Vulnerability | Configured Sensitive Codepath Modified by Non-Allowed Author |
|---|---|
| Description | File 'dojo/utils.py' matches configured sensitive codepath pattern 'dojo/utils.py' and was modified by 'claude' (commit 0b312b0) who is not in the allowed authors list. |
We've notified @mtesauro.
Comment to provide feedback on these findings.
Report false positive: @dryrunsecurity fp [FINDING ID] [FEEDBACK]
Report low-impact: @dryrunsecurity nit [FINDING ID] [FEEDBACK]
Example: @dryrunsecurity fp drs_90eda195 This code is not user-facing
All finding details can be found in the DryRun Security Dashboard.
|
CI note: this PR was removed from the merge queue on a single, unrelated unit-test failure in the That is not caused by this change. This PR only reorders tagulous tag-count decrements during No code change is warranted here — re-queuing should clear it. Generated by Claude Code |
# Conflicts: # unittests/test_tag_utils_bulk.py
|
CI note (2nd dequeue): the merge-queue failure this time was the UI job This looks like the same Selenium flakiness the queue is seeing across PRs right now — several other PRs (#15616, #15650, #15671) failed their merge-queue No code change is warranted — re-queuing should clear it, as it has for the other PRs. Generated by Claude Code |
Description
A single-object finding delete —
DELETE /api/v2/findings/{id}/→Finding.delete()→super().delete()— surfaced a Postgres deadlock to the client:Root cause.
dojo_tagulous_finding_tagsis the tagulous tag table (it holdscount), so the deadlocked statement is the per-tagcount = count - 1UPDATE, which takes a row lock on each tag. The synchronous single-object delete does not clear tags itself; it relies onsuper().delete(), whosepre_deletefires tagulous's per-objectclear(). Thatclear()issues one count UPDATE per tag in the manager's own iteration order, not in ascending tag-id order. Because the UPDATE order is the lock order, two concurrent single-finding deletes whose tag sets overlap can take the same tag-row locks in opposite orders, forming a cycle Postgres breaks by aborting one side (SQLSTATE40P01).This is the single-object twin of two deadlocks already fixed for the other tag-count paths:
bulk_remove_all_tags) decrement counts in a deterministic ascending tag-id order, and added anasync_delete_taskretry backstop.The synchronous
Finding.delete()path — used by the RESTdestroy()in both open source and the Pro override, which callssuper().destroy()→instance.delete()— was never routed through that shared ordering, so it can still deadlock against a concurrent single delete, bulk delete, or import.Fix.
Finding.delete()now removes the finding's tags viabulk_remove_all_tags(Finding, Finding.objects.filter(pk=self.pk))beforesuper().delete(). This issues the count decrements in the same ascending tag-id order every other tag-count path already uses, so all callers acquire the tag-row locks in one shared order and the cycle cannot form. Clearing the through rows first also leaves the tagulouspre_deletehandler nothing to decrement, so tag counts are not double-processed. Behavior is now consistent with the bulk cascade delete, which is the dominant deletion path.No migration. This is a pure ordering change to an existing code path — no model or schema change. A migration was neither generated nor needed; the fix reuses the already-shipped
bulk_remove_all_tagshelper.Downstream (Pro) impact. DefectDojo Pro's
FindingViewSet.destroy()overrides the OSS one but delegates tosuper().destroy(), which calls the model-levelFinding.delete()— so the fix covers the Pro delete path with no Pro-side change. Pro'sFindingpre_deletereceiver only dispatches integrator notifications and does not touch tag counts. Pro does not subclassFindingor overrideFinding.delete().Test results
Added
FindingDeleteTagLockOrderTestinunittests/test_tag_utils_bulk.py, mirroring the existingBulkRemoveAllTagsLockOrderTest(#15486) andBulkAddTagsToInstancesLockOrderTest(#15652). Three tags are attached to a finding in an order unrelated to their ids; the test captures the tag rows locked by the count UPDATEs duringfinding.delete()and asserts they are issued in ascending tag-id order. It fails on the old per-object-clear()order (name order,[2,3,1]) and passes once the delete is routed throughbulk_remove_all_tags([1,2,3]).The test is deterministic — it asserts the update/lock ordering, not a live race. As with #15652, a full Postgres-backed suite run could not be stood up in the ephemeral environment used to author this change; validation is being carried through CI.
ruff checkclean on both changed files (0.16.0, reporuff.toml).Documentation
No documentation change: no user-visible behavior change and no new setting.
Checklist
bugfixbranch.Generated by Claude Code