Skip to content

Commit 3975e95

Browse files
authored
Include analysis.detail in DefectDojo finding payload (#6181)
* test(model): add failing tests for analysis.detail in Finding Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * feat(model): include analysis.detail in Finding query and constructor Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * fix(model): update QUERY_ALL_FINDINGS project UUID index after DETAILS column shift Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * test(integrations): verify analysis.detail propagates through QUERY_ALL_FINDINGS Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * docs(integrations): document forwarded analysis fields for DefectDojo Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * docs(integrations): clarify how analysis.state is handled by DefectDojo Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * Updated FPF_VERSION and added a section to file_formats.md Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * fix(test): update FPF version assertions from 1.3 to 1.4 Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * docs(integrations): fix FPF v1.4 entry in file-formats.md Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> * Remove 'not yet available' note from FPF v1.4 docs Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> --------- Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com>
1 parent d1adc69 commit 3975e95

9 files changed

Lines changed: 61 additions & 18 deletions

File tree

docs/_docs/integrations/defectdojo.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ Dependency-Track accomplishes this in the following ways:
1414
* Dependency-Track pushes findings to DefectDojo on a periodic basis (configurable)
1515
* DefectDojo parses Dependency-Track findings
1616

17+
### Forwarded analysis data
18+
19+
Each finding pushed to DefectDojo includes audit data from Dependency-Track. The following fields influence how DefectDojo represents the finding:
20+
21+
| Field | Description |
22+
| ---------------------- | ----------- |
23+
| `analysis.state` | A state of `FALSE_POSITIVE` marks the finding as a false positive in DefectDojo. Other states are included in the payload but are not acted on by the DefectDojo parser. |
24+
| `analysis.isSuppressed`| Whether the finding is suppressed in Dependency-Track. |
25+
| `analysis.detail` | Free-text analyst notes entered in Dependency-Track. Appended to the finding description in DefectDojo. Requires Dependency-Track v4.14.0 or higher and a compatible version of the DefectDojo Dependency Track parser. |
26+
1727
Requirements:
1828
* Dependency-Track v4.1.0 or higher
1929
* DefectDojo 1.13.1 or higher

docs/_docs/integrations/file-formats.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ The **VIEW_VULNERABILITY** permission is required to use the findings API.
3939
> It adds optional `cvssV2Vector`, `cvssV3Vector`, `cvssV4Vector`, and `owaspRRVector` fields to `vulnerability` objects,
4040
> which are included when the corresponding scores or ratings are available and may be omitted otherwise.
4141
42+
> Finding Packaging Format v1.4 adds an optional `detail` field to `analysis` objects, which contains any analyst notes recorded against the finding.
43+
4244
#### Example
4345

4446
```json
4547
{
46-
"version": "1.3",
48+
"version": "1.4",
4749
"meta" : {
4850
"application": "Dependency-Track",
4951
"version": "4.5.0",
@@ -89,7 +91,8 @@ The **VIEW_VULNERABILITY** permission is required to use the findings API.
8991
},
9092
"analysis": {
9193
"state": "NOT_SET",
92-
"isSuppressed": false
94+
"isSuppressed": false,
95+
"detail": "Reviewed and confirmed not exploitable in this context."
9396
},
9497
"matrix": "ca4f2da9-0fad-4a13-92d7-f627f3168a56:b815b581-fec1-4374-a871-68862a8f8d52:115b80bb-46c4-41d1-9f10-8a175d4abb46"
9598
},

src/main/java/org/dependencytrack/integrations/FindingPackagingFormat.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
public class FindingPackagingFormat {
3636

3737
/** FPF is versioned. If the format changes, the version needs to be bumped. */
38-
private static final String FPF_VERSION = "1.3";
38+
private static final String FPF_VERSION = "1.4";
3939
private static final String FIELD_APPLICATION = "application";
4040
private static final String FIELD_VERSION = "version";
4141
private static final String FIELD_TIMESTAMP = "timestamp";

src/main/java/org/dependencytrack/model/Finding.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public class Finding implements Serializable {
9696
, "FINDINGATTRIBUTION"."REFERENCE_URL"
9797
, "ANALYSIS"."STATE"
9898
, "ANALYSIS"."SUPPRESSED"
99+
, "ANALYSIS"."DETAILS"
99100
FROM "COMPONENT"
100101
INNER JOIN "COMPONENTS_VULNERABILITIES"
101102
ON "COMPONENT"."ID" = "COMPONENTS_VULNERABILITIES"."COMPONENT_ID"
@@ -151,6 +152,7 @@ public class Finding implements Serializable {
151152
, "FINDINGATTRIBUTION"."REFERENCE_URL"
152153
, "ANALYSIS"."STATE"
153154
, "ANALYSIS"."SUPPRESSED"
155+
, "ANALYSIS"."DETAILS"
154156
, "PROJECT"."UUID"
155157
, "PROJECT"."NAME"
156158
, "PROJECT"."VERSION"
@@ -245,10 +247,15 @@ public Finding(UUID project, Object... o) {
245247

246248
optValue(analysis, "state", o[34]);
247249
optValue(analysis, "isSuppressed", o[35], false);
250+
if (o[36] instanceof final Clob clob) {
251+
optValue(analysis, "detail", toString(clob));
252+
} else {
253+
optValue(analysis, "detail", o[36]);
254+
}
248255

249-
if (o.length > 36) {
250-
optValue(component, "projectName", o[37]);
251-
optValue(component, "projectVersion", o[38]);
256+
if (o.length > 37) {
257+
optValue(component, "projectName", o[38]);
258+
optValue(component, "projectVersion", o[39]);
252259
}
253260
}
254261

src/main/java/org/dependencytrack/persistence/FindingsSearchQueryManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public PaginatedResult getAllFindings(final Map<String, String> filters, final b
137137
final List<Object[]> list = totalList.subList(this.pagination.getOffset(), Math.min(this.pagination.getOffset() + this.pagination.getLimit(), totalList.size()));
138138
final List<Finding> findings = new ArrayList<>();
139139
for (final Object[] o : list) {
140-
final Finding finding = new Finding(UUID.fromString((String) o[36]), o);
140+
final Finding finding = new Finding(UUID.fromString((String) o[37]), o);
141141
final Component component = getObjectByUuid(Component.class, (String) finding.getComponent().get("uuid"));
142142
final Vulnerability vulnerability = getObjectByUuid(Vulnerability.class, (String) finding.getVulnerability().get("uuid"));
143143
final Analysis analysis = getAnalysis(component, vulnerability);

src/test/java/org/dependencytrack/integrations/FindingPackagingFormatTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void wrapperTest() {
6060
Assertions.assertEquals(project.getDescription(), pjson.getString("description"));
6161
Assertions.assertEquals(project.getVersion(), pjson.getString("version"));
6262

63-
Assertions.assertEquals("1.3", root.getString("version"));
63+
Assertions.assertEquals("1.4", root.getString("version"));
6464
}
6565

6666
@Test
@@ -92,7 +92,8 @@ void testFindingsVulnerabilityAndAliases() {
9292
null, // 32
9393
null, // 33
9494
AnalysisState.NOT_AFFECTED, // 34
95-
true // 35
95+
true, // 35
96+
null // 36
9697
);
9798

9899
Finding findingWithAlias = new Finding(project.getUuid(), "component-uuid-2", "component-name-2", "component-group",
@@ -119,7 +120,8 @@ void testFindingsVulnerabilityAndAliases() {
119120
null, // 32
120121
null, // 33
121122
AnalysisState.NOT_AFFECTED, // 34
122-
true // 35
123+
true, // 35
124+
null // 36
123125
);
124126

125127
var alias = new VulnerabilityAlias();

src/test/java/org/dependencytrack/model/FindingTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class FindingTest extends PersistenceCapableTest {
5858
null, // 32
5959
null, // 33
6060
AnalysisState.NOT_AFFECTED, // 34
61-
true // 35
61+
true, // 35
62+
"analysis-detail" // 36
6263
);
6364

6465
@Test
@@ -102,6 +103,7 @@ void testAnalysis() {
102103
Map<String, Object> map = finding.getAnalysis();
103104
Assertions.assertEquals(AnalysisState.NOT_AFFECTED, map.get("state"));
104105
Assertions.assertEquals(true, map.get("isSuppressed"));
106+
Assertions.assertEquals("analysis-detail", map.get("detail"));
105107
}
106108

107109
@Test

src/test/java/org/dependencytrack/resources/v1/FindingResourceTest.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import jakarta.ws.rs.core.Response;
3232
import org.dependencytrack.JerseyTestExtension;
3333
import org.dependencytrack.ResourceTest;
34+
import org.dependencytrack.model.AnalysisState;
3435
import org.dependencytrack.model.Component;
3536
import org.dependencytrack.model.ConfigPropertyConstants;
3637
import org.dependencytrack.model.Project;
@@ -193,7 +194,7 @@ void exportFindingsByProjectTest() {
193194
Assertions.assertEquals("Acme Example", json.getJsonObject("project").getString("name"));
194195
Assertions.assertEquals("1.0", json.getJsonObject("project").getString("version"));
195196
Assertions.assertEquals(p1.getUuid().toString(), json.getJsonObject("project").getString("uuid"));
196-
Assertions.assertEquals("1.3", json.getString("version")); // FPF version
197+
Assertions.assertEquals("1.4", json.getString("version")); // FPF version
197198
JsonArray findings = json.getJsonArray("findings");
198199
Assertions.assertEquals(3, findings.size());
199200
Assertions.assertEquals("Component A", findings.getJsonObject(0).getJsonObject("component").getString("name"));
@@ -419,6 +420,24 @@ void getAllFindings() {
419420
Assertions.assertEquals(p2.getUuid().toString(), json.getJsonObject(4).getJsonObject("component").getString("project"));
420421
}
421422

423+
@Test
424+
void getAllFindingsIncludesAnalysisDetail() {
425+
Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false);
426+
Component component = createComponent(project, "Component A", "1.0");
427+
Vulnerability vuln = createVulnerability("Vuln-1", Severity.HIGH);
428+
qm.addVulnerability(vuln, component, AnalyzerIdentity.NONE);
429+
qm.makeAnalysis(component, vuln, AnalysisState.NOT_AFFECTED, null, null, "audit detail text", false);
430+
431+
Response response = jersey.target(V1_FINDING).request()
432+
.header(X_API_KEY, apiKey)
433+
.get(Response.class);
434+
435+
Assertions.assertEquals(200, response.getStatus());
436+
JsonArray json = parseJsonArray(response);
437+
Assertions.assertEquals(1, json.size());
438+
Assertions.assertEquals("audit detail text", json.getJsonObject(0).getJsonObject("analysis").getString("detail"));
439+
}
440+
422441
@Test
423442
void getAllFindingsWithAclEnabled() {
424443
Project p1 = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false);

src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ void testUpload() {
147147
.withName("file")
148148
.withBody(equalToJson("""
149149
{
150-
"version": "1.3",
150+
"version": "1.4",
151151
"meta": {
152152
"application": "Dependency-Track",
153153
"version": "${json-unit.any-string}",
@@ -279,7 +279,7 @@ void testUploadWithTestName() {
279279
.withName("file")
280280
.withBody(equalToJson("""
281281
{
282-
"version": "1.3",
282+
"version": "1.4",
283283
"meta": {
284284
"application": "Dependency-Track",
285285
"version": "${json-unit.any-string}",
@@ -527,7 +527,7 @@ void testUploadWithGlobalReimport() {
527527
.withName("file")
528528
.withBody(equalToJson("""
529529
{
530-
"version": "1.3",
530+
"version": "1.4",
531531
"meta": {
532532
"application": "Dependency-Track",
533533
"version": "${json-unit.any-string}",
@@ -674,7 +674,7 @@ void testUploadWithProjectLevelReimport() {
674674
.withName("file")
675675
.withBody(equalToJson("""
676676
{
677-
"version": "1.3",
677+
"version": "1.4",
678678
"meta": {
679679
"application": "Dependency-Track",
680680
"version": "${json-unit.any-string}",
@@ -801,7 +801,7 @@ void testUploadWithProjectLevelReimportAndTestName() {
801801
.withName("file")
802802
.withBody(equalToJson("""
803803
{
804-
"version": "1.3",
804+
"version": "1.4",
805805
"meta": {
806806
"application": "Dependency-Track",
807807
"version": "${json-unit.any-string}",
@@ -893,7 +893,7 @@ void testUploadWithReimportAndNoExistingTest() {
893893
.withName("file")
894894
.withBody(equalToJson("""
895895
{
896-
"version": "1.3",
896+
"version": "1.4",
897897
"meta": {
898898
"application": "Dependency-Track",
899899
"version": "${json-unit.any-string}",

0 commit comments

Comments
 (0)