Skip to content

bugfix: use subquery for (finding) counts#12784

Merged
Maffooch merged 4 commits into
DefectDojo:bugfixfrom
valentijnscholten:view-test-subquery-count
Jul 21, 2025
Merged

bugfix: use subquery for (finding) counts#12784
Maffooch merged 4 commits into
DefectDojo:bugfixfrom
valentijnscholten:view-test-subquery-count

Conversation

@valentijnscholten

@valentijnscholten valentijnscholten commented Jul 15, 2025

Copy link
Copy Markdown
Member

On Slack a user reported an exception involving an invalid Postgres query in Defect Dojo 2.47.3.

4448] ERROR:  column "dojo_finding.title" must appear in the GROUP BY clause or be used in an aggregate function at characte │
│ r 29                                                                                                                                                      │
│ 2025-07-14 15:05:54.553 GMT [4448] STATEMENT:  SELECT "dojo_finding"."id", "dojo_finding"."title", "dojo_finding"."date", "dojo_finding"."sla_start_date" │
│ , "dojo_finding"."sla_expiration_date", "dojo_finding"."cwe", "dojo_finding"."cve", "dojo_finding"."epss_score", "dojo_finding"."epss_percentile", "dojo_ │
│ finding"."cvssv3", "dojo_finding"."cvssv3_score", "dojo_finding"."url", "dojo_finding"."severity", "dojo_finding"."description", "dojo_finding"."mitigati │
│ on", "dojo_finding"."impact", "dojo_finding"."steps_to_reproduce", "dojo_finding"."severity_justification", "dojo_finding"."refs", "dojo_finding"."test_i │
│ d", "dojo_finding"."active", "dojo_finding"."verified", "dojo_finding"."false_p", "dojo_finding"."duplicate", "dojo_finding"."duplicate_finding_id", "doj │
│ o_finding"."out_of_scope", "dojo_finding"."risk_accepted", "dojo_finding"."under_review", "dojo_finding"."last_status_update", "dojo_finding"."review_req │
│ uested_by_id", "dojo_finding"."under_defect_review", "dojo_finding"."defect_review_requested_by_id", "dojo_finding"."is_mitigated", "dojo_finding"."threa │
│ d_id", "dojo_finding"."mitigated", "dojo_finding"."mitigated_by_id", "dojo_finding"."reporter_id", "dojo_finding"."numerical_severity", "dojo_finding"."l │
│ ast_reviewed", "dojo_finding"."last_reviewed_by_id", "dojo_finding"."param", "dojo_finding"."payload", "dojo_finding"."hash_code", "dojo_finding"."line", │
│  "dojo_finding"."file_path", "dojo_finding"."component_name", "dojo_finding"."component_version", "dojo_finding"."static_finding", "dojo_finding"."dynami │
│ c_finding", "dojo_finding"."created", "dojo_finding"."scanner_confidence", "dojo_finding"."sonarqube_issue_id", "dojo_finding"."unique_id_from_tool", "do │
│ jo_finding"."vuln_id_from_tool", "dojo_finding"."sast_source_object", "dojo_finding"."sast_sink_object", "dojo_finding"."sast_source_line", "dojo_finding │
│ "."sast_source_file_path", "dojo_finding"."nb_occurences", "dojo_finding"."publish_date", "dojo_finding"."service", "dojo_finding"."planned_remediation_d │
│ ate", "dojo_finding"."planned_remediation_version", "dojo_finding"."effort_for_fixing", COUNT("dojo_endpoint_status"."id") FILTER (WHERE NOT "dojo_endpoi │
│ nt_status"."mitigated") AS "active_endpoint_count", COUNT("dojo_endpoint_status"."id") FILTER (WHERE "dojo_endpoint_status"."mitigated") AS "mitigated_en │
│ dpoint_count" FROM "dojo_finding" LEFT OUTER JOIN "dojo_endpoint_status" ON ("dojo_finding"."id" = "dojo_endpoint_status"."finding_id") GROUP BY "dojo_fi │
│ nding"."id" ORDER BY "dojo_finding"."numerical_severity" ASC LIMIT 21

This code hasn't been changed since a long time, so it's unclear why this pops up now. It could be related to a very specific dataset or data that was create long ago and was migrated over time in Django migrations.

Either way, this code is using the old aggrgation via group by constructs. In 2.48.0 via #12575 these were changed into subquery counts. This was not done for view_test where a list of findings is also rendered. This used its own almost identical copy of prefetch_for_findings which still used the old construct.

prefetched_findings = prefetched_findings.annotate(active_endpoint_count=Count("status_finding__id", filter=Q(status_finding__mitigated=False)))
prefetched_findings = prefetched_findings.annotate(mitigated_endpoint_count=Count("status_finding__id", filter=Q(status_finding__mitigated=True)))

This PR changes view_test to use that existing and optimized prefetch_for_findings method that doesn't use aggregation via group by anymore. I had to move around some methods to avoid circular imports.

ADDED: The use on Slack reported more group by errors in other places. It's almost as if their Postgres is more strict than what we are using? These code paths haven't changed in years and have been running fine. Still it is good if we adopt the code base to comply with these group by rules. But as we have been moving from Count with GROUP BY to using subqueries, I have updated all remaining Count instances to use subqueries insead. Should get rid of the GROUP BY errors and improve performance for large datasets.

@valentijnscholten valentijnscholten added this to the 2.48.2 milestone Jul 15, 2025
@valentijnscholten valentijnscholten marked this pull request as ready for review July 15, 2025 17:17
@dryrunsecurity

dryrunsecurity Bot commented Jul 15, 2025

Copy link
Copy Markdown

DryRun Security

This pull request contains three low-risk findings: a potential subquery injection vulnerability in query utilities, a CVSS parsing function that might return None unexpectedly, and a potential configuration risk from adding .cursor-rules to the gitignore file, none of which are considered blocking issues.

Potential Subquery Injection in dojo/query_utils.py
Vulnerability Potential Subquery Injection
Description In the build_count_subquery function in dojo/query_utils.py, the group_field parameter is directly used in model_qs.values() without validation. While Django's ORM provides protection, there's a theoretical risk of information disclosure or unexpected query behavior if an attacker can control the input.

from django.db.models import Count, IntegerField, Subquery
from django.db.models.query import QuerySet
def build_count_subquery(model_qs: QuerySet, group_field: str) -> Subquery:
"""Return a Subquery that yields one aggregated count per `group_field`."""
return Subquery(
model_qs.values(group_field).annotate(c=Count("*")).values("c")[:1], # one row per group_field
output_field=IntegerField(),
)

Potential CVSS Parsing Error in dojo/utils.py
Vulnerability Potential CVSS Parsing Error
Description In the parse_cvss_data function in dojo/utils.py, the removal of the explicit return {} means the function will now implicitly return None when no valid CVSS3 vector is found. This could lead to unexpected behavior in downstream code that expects a dictionary, potentially causing errors in security-sensitive contexts like risk calculation.

}
logger.debug("No valid CVSS3 vector found in %s", cvss_vector_string)
return {}

Potential Gitignore Configuration Risk in .gitignore
Vulnerability Potential Gitignore Configuration Risk
Description The addition of .cursor-rules to .gitignore could potentially lead to configuration drift or inconsistent security settings if this file contains critical configuration that should be version-controlled. The risk depends on the specific contents of the .cursor-rules file.

docs/.devcontainer/Dockerfile
docs/LICENSE
docs/.hugo_build.lock
.cursor-rules


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

@valentijnscholten valentijnscholten changed the title view_test: use subquery for finding counts bugfix: view_test: use subquery for finding counts Jul 15, 2025
@valentijnscholten valentijnscholten changed the title bugfix: view_test: use subquery for finding counts bugfix: use subquery for (finding) counts Jul 16, 2025

@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

@valentijnscholten valentijnscholten added the performance performance improvement or bug report label Jul 20, 2025
@Maffooch Maffooch merged commit 0948ad3 into DefectDojo:bugfix Jul 21, 2025
85 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance performance improvement or bug report

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants