Skip to content

Commit 75e7834

Browse files
fix(dependency_check): fold related dependency paths into description (#14941)
Dependency-Check's DependencyBundlingAnalyzer merges co-grouped artifacts into one main dependency and lists the others under <relatedDependencies>. The vulnerability is attached only to the main dependency in the XML; related entries are metadata for other files in the same logical component. Previously the parser emitted one finding per related entry in addition to the main finding. This multiplied a single CVE into N findings sharing the same title, CVE, component name, and version — only the file path differed. Projects with Spring Boot, ActiveMQ, or other libraries whose CPE matches many sibling artifacts (DC bundling scenario 4) were hit hardest. Instead, emit one finding per vulnerability per main dependency and surface related file paths in the description under a "**Related Filepaths:**" block. The five DependencyBundlingAnalyzer bundling scenarios are documented in the new build_related_dependencies_block() helper, the parser docs page, and the 2.59.1 upgrade notes. Closes-style note: findings previously tagged `related` will be closed on the next reimport as they are no longer emitted. The `related` tag is not applied.
1 parent 136f54f commit 75e7834

4 files changed

Lines changed: 193 additions & 150 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
title: 'Upgrading to DefectDojo Version 2.59.1'
3+
toc_hide: true
4+
weight: -20260603
5+
description: Dependency Check parser no longer emits separate findings for related dependencies; related file paths are now listed in the main finding's description.
6+
---
7+
8+
## Dependency Check parser: related dependencies folded into the main finding
9+
10+
The Dependency Check parser previously created one finding per `<relatedDependency>` in the report, in addition to the finding for the main vulnerable dependency. Because OWASP Dependency-Check attaches the vulnerability only to the main dependency in the XML and the related entries are metadata pointing to other files in the same logical component, this produced N findings sharing the same title, CVE, component name and component version — only the file path differed. For projects with Spring Boot, ActiveMQ, or other libraries whose CPE matches many sibling artifacts this produced significant noise.
11+
12+
Starting in 2.59.1, the parser emits exactly one finding per vulnerability per main dependency. The file paths of any related dependencies are surfaced in the finding description under a `**Related Filepaths:**` block.
13+
14+
### Background: what `<relatedDependencies>` actually contains
15+
16+
OWASP Dependency-Check's `DependencyBundlingAnalyzer` merges co-grouped artifacts into a single main dependency and lists the others under `<relatedDependencies>`. It does this under five scenarios:
17+
18+
1. **Identical content (`hashesMatch`)** — the same jar (matching sha1) found at multiple paths, e.g. the same library packaged into multiple ear/war archives.
19+
2. **Shaded jar (`isShadedJar`)** — a `.jar` and a `pom.xml` extracted from inside it share the same CPE; the pom.xml is recorded as related.
20+
3. **WebJar (`isWebJar`)** — a `.js` file extracted from a WebJar matches the jar's CPE (mapped via `pkg:maven/org.webjars/<name>@<version>`); the js file is recorded as related to the jar.
21+
4. **Same CPE + base path + vulnerabilities + filename match** — sibling artifacts in the same project that share a CPE. Example: `spring-boot`, `spring-boot-actuator`, `spring-boot-starter-jdbc`, etc. all map to the `spring_boot` CPE and are grouped under the main `spring-boot` jar.
22+
5. **NPM same name + version** — the same npm package discovered via different resolution paths (for example `package-lock.json` plus `node_modules`).
23+
24+
Only scenario 1 represents the same vulnerable artifact at multiple deploy locations. Scenarios 2-5 are different files representing one logical component. Both cases were previously inflated into separate findings; both now collapse to one finding with the related paths listed in the description.
25+
26+
### Required actions
27+
28+
- **Users filtering or grouping by the `related` tag**: that tag is no longer applied because related findings are no longer created. Update any saved filters, dashboards, or rules that depend on it. Equivalent information is now available in the finding description (look for `**Related Filepaths:**`).
29+
- **Reimport behavior**: on the next reimport of an existing Dependency Check report, the previously-created `related` findings will be closed as no longer present in the report. This is expected and matches the new parsing behavior.
30+
31+
32+
For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.59.1).

docs/content/supported_tools/parsers/file/dependency_check.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,21 @@ OWASP Dependency Check output can be imported in Xml format. This parser ingests
77
* Suppressed vulnerabilities are tagged with the tag: `suppressed`.
88
* Suppressed vulnerabilities are marked as mitigated.
99
* If the suppression is missing any `<notes>` tag, it tags them as `no_suppression_document`.
10-
* Related vulnerable dependencies are tagged with `related` tag.
10+
11+
### Related dependencies
12+
13+
OWASP Dependency-Check's `DependencyBundlingAnalyzer` merges co-grouped artifacts into a single main dependency and lists the others under `<relatedDependencies>` in the report. The vulnerability is attached only to the main dependency; the related entries are metadata pointing to other files in the same logical component.
14+
15+
The parser emits one finding per vulnerability per main dependency and surfaces the related file paths in the finding's description under a `**Related Filepaths:**` block (rather than creating a separate finding per related entry, which produced N findings sharing the same title and CVE differing only in file path).
16+
17+
DC bundles dependencies under five scenarios:
18+
19+
1. **Identical content (`hashesMatch`)** — the same jar (matching sha1) found at multiple paths (e.g. packaged into multiple ear/war archives).
20+
2. **Shaded jar (`isShadedJar`)** — a `.jar` and a `pom.xml` extracted from inside it share the same CPE.
21+
3. **WebJar (`isWebJar`)** — a `.js` file extracted from a WebJar maps to the jar's CPE via `pkg:maven/org.webjars/<name>@<version>`.
22+
4. **Same CPE + base path + vulnerabilities + filename match** — sibling artifacts sharing a CPE (e.g. `spring-boot`, `spring-boot-actuator`, `spring-boot-starter-jdbc` all map to the `spring_boot` CPE).
23+
5. **NPM same name + version** — the same npm package discovered via different resolution paths (e.g. `package-lock.json` + `node_modules`).
24+
1125

1226
### Sample Scan Data
1327
Sample Dependency Check scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/dependency_check).

dojo/tools/dependency_check/parser.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,57 @@ def add_finding(self, finding, dupes):
8686
if key not in dupes:
8787
dupes[key] = finding
8888

89+
def build_related_dependencies_block(self, dependency, namespace):
90+
"""
91+
Return a markdown block listing related dependencies, or ''.
92+
93+
Dependency-Check's DependencyBundlingAnalyzer merges co-grouped artifacts
94+
into one main dependency and lists the others under <relatedDependencies>.
95+
The vulnerability is attached only to the main dependency in the XML; the
96+
related entries are metadata pointing to other files in the same logical
97+
component. Previously we emitted one finding per related entry, which
98+
multiplied a single CVE into N findings sharing the same title and CVE
99+
with only the file_path differing — pure noise for the user. Instead we
100+
keep one finding for the main dependency and surface the related file
101+
paths in its description.
102+
103+
DC bundles dependencies under five scenarios (see
104+
DependencyBundlingAnalyzer.evaluateDependencies):
105+
106+
1. hashesMatch — identical content (same sha1) found at multiple paths,
107+
e.g. the same jar packaged into multiple ear/war archives.
108+
2. isShadedJar — a .jar and a pom.xml extracted from inside it share the
109+
same CPE; the pom.xml is recorded as related to the jar.
110+
3. isWebJar — a .js file extracted from a WebJar matches the jar's CPE
111+
(mapped via pkg:maven/org.webjars/<name>@<version>); the js is
112+
related to the jar.
113+
4. CPE + base path + vulnerabilities + filename match — sibling artifacts
114+
in the same project that share a CPE (e.g. spring-boot,
115+
spring-boot-actuator, spring-boot-starter all map to the
116+
spring_boot CPE).
117+
5. NPM same name + version — the same npm package discovered via
118+
different resolution paths (e.g. package-lock.json + node_modules).
119+
120+
Scenario 1 is the only case where the related entries are genuinely
121+
separate vulnerable locations; scenarios 2-5 are different files
122+
representing one logical component. Listing all related paths in the
123+
description preserves the per-location information for scenario 1 while
124+
removing the noise from scenarios 2-5.
125+
"""
126+
related = dependency.find(namespace + "relatedDependencies")
127+
if related is None:
128+
return ""
129+
entries = []
130+
for rd in related.findall(namespace + "relatedDependency"):
131+
file_name = rd.findtext(f"{namespace}fileName")
132+
if not file_name:
133+
continue
134+
file_path = (rd.findtext(f"{namespace}filePath") or "").strip()
135+
entries.append(f"- {file_name} ({file_path})" if file_path else f"- {file_name}")
136+
if not entries:
137+
return ""
138+
return "\n**Related Filepaths:**\n" + "\n".join(entries)
139+
89140
def get_filename_and_path_from_dependency(
90141
self,
91142
dependency,
@@ -448,6 +499,7 @@ def get_findings(self, filename, test):
448499
namespace + "vulnerabilities",
449500
)
450501
if vulnerabilities is not None:
502+
related_block = self.build_related_dependencies_block(dependency, namespace)
451503
for vulnerability in vulnerabilities.findall(
452504
namespace + "vulnerability",
453505
):
@@ -459,29 +511,12 @@ def get_findings(self, filename, test):
459511
test,
460512
namespace,
461513
)
514+
if related_block:
515+
finding.description += related_block
462516
if scan_date:
463517
finding.date = scan_date
464518
self.add_finding(finding, dupes)
465519

466-
relatedDependencies = dependency.find(
467-
namespace + "relatedDependencies",
468-
)
469-
if relatedDependencies is not None:
470-
for relatedDependency in relatedDependencies.findall(
471-
namespace + "relatedDependency",
472-
):
473-
finding = self.get_finding_from_vulnerability(
474-
dependency,
475-
relatedDependency,
476-
vulnerability,
477-
test,
478-
namespace,
479-
)
480-
if finding: # could be None
481-
if scan_date:
482-
finding.date = scan_date
483-
self.add_finding(finding, dupes)
484-
485520
for suppressedVulnerability in vulnerabilities.findall(
486521
namespace + "suppressedVulnerability",
487522
):
@@ -493,6 +528,8 @@ def get_findings(self, filename, test):
493528
test,
494529
namespace,
495530
)
531+
if related_block:
532+
finding.description += related_block
496533
if scan_date:
497534
finding.date = scan_date
498535
self.add_finding(finding, dupes)

0 commit comments

Comments
 (0)