Skip to content

Commit b9ea94f

Browse files
authored
chore: fix presentation for suppressions pending approval (#568)
1 parent a4cb5e5 commit b9ea94f

File tree

5 files changed

+225
-31
lines changed

5 files changed

+225
-31
lines changed

internal/presenters/testdata/ufm/secrets.human.readable

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
Testing () ...
22

3-
Open Secrets issues: 1
3+
Open Secrets issues: 2
44

55
✗ [HIGH] Generic API Key
66
Finding ID: 99999999-8888-7777-6665-000000000000
77
Info: Do not hardcode passwords or other secrets directly in the source code. Use a secure secret management system instead.
88
Path: config.env, line 3 to 44
99

10+
! [PENDING IGNORE...] [CRITICAL] Generic Secret
11+
Finding ID: bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee
12+
Info: Do not hardcode passwords or other secrets directly in the source code. Use a secure secret management system instead.
13+
Path: aws_keys.txt, line 4 to 4
14+
15+
Expiration: September 09, 2027
16+
Category: wont-fix
17+
Ignored on: February 05, 2026
18+
Ignored by: Someone
19+
Reason: Someone is ignoring this
20+
1021
Ignored Secrets issues: 1
1122

1223
! [IGNORED] [HIGH] Generic API Key
@@ -24,8 +35,8 @@
2435
│ Test type: Secret Detection │
2536
│ Project path: │
2637
│ │
27-
│ Total secrets issues: 2
38+
│ Total secrets issues: 3
2839
│ Ignored: 1 [ 0 CRITICAL  1 HIGH  0 MEDIUM  0 LOW ] │
29-
│ Open : 1 [ 0 CRITICAL  1 HIGH  0 MEDIUM  0 LOW ] │
40+
│ Open : 2 [ 1 CRITICAL  1 HIGH  0 MEDIUM  0 LOW ] │
3041
╰─────────────────────────────────────────────────────────╯
3142

internal/presenters/testdata/ufm/secrets.sarif.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@
4242
"CWE-798"
4343
]
4444
}
45+
},
46+
{
47+
"id": "generic-secret",
48+
"shortDescription": {
49+
"text": "Detected a generic secret, which could lead to unauthorized access and sensitive data exposure."
50+
},
51+
"help": {
52+
"text": "",
53+
"markdown": "Do not hardcode passwords or other secrets directly in the source code. Use a secure secret management system instead."
54+
},
55+
"properties": {
56+
"tags": [
57+
"security",
58+
"CWE-798"
59+
]
60+
}
4561
}
4662
]
4763
}
@@ -98,6 +114,36 @@
98114
"kind": "external"
99115
}
100116
]
117+
},
118+
{
119+
"ruleId": "generic-secret",
120+
"level": "error",
121+
"message": {
122+
"text": "This file contains a critical severity Generic Secret vulnerability."
123+
},
124+
"locations": [
125+
{
126+
"physicalLocation": {
127+
"artifactLocation": {
128+
"uri": "aws_keys.txt"
129+
},
130+
"region": {
131+
"startLine": 4,
132+
"startColumn": 35,
133+
"endLine": 4,
134+
"endColumn": 75
135+
}
136+
}
137+
}
138+
],
139+
"suppressions": [
140+
{
141+
"guid": "90b2fe3f-f811-4447-8f75-e32545d753ea",
142+
"status": "underReview",
143+
"justification": "Someone is ignoring this",
144+
"kind": "external"
145+
}
146+
]
101147
}
102148
],"automationDetails": {
103149
"id": "Snyk/Secrets/0/2026-02-03T11:03:16Z"

internal/presenters/testdata/ufm/secrets.testresult.json

Lines changed: 98 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,7 @@
6868
"source": "cwe"
6969
},
7070
"generic-api-key": {
71-
"categories": [
72-
"Security"
73-
],
71+
"categories": ["Security"],
7472
"help": "Do not hardcode passwords or other secrets directly in the source code. Use a secure secret management system instead.",
7573
"id": "generic-api-key",
7674
"name": "Generic API Key",
@@ -81,9 +79,7 @@
8179
"tags": []
8280
},
8381
"generic-api-key-2": {
84-
"categories": [
85-
"Security"
86-
],
82+
"categories": ["Security"],
8783
"help": "Do not hardcode passwords or other secrets directly in the source code. Use a secure secret management system instead.",
8884
"id": "generic-api-key-2",
8985
"name": "Generic API Key",
@@ -92,17 +88,23 @@
9288
"short_description": "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
9389
"source": "secret",
9490
"tags": []
91+
},
92+
"generic-secret": {
93+
"categories": ["Security"],
94+
"help": "Do not hardcode passwords or other secrets directly in the source code. Use a secure secret management system instead.",
95+
"id": "generic-secret",
96+
"name": "Generic Secret",
97+
"precision": "very-high",
98+
"severity": "high",
99+
"short_description": "Detected a generic secret, which could lead to unauthorized access and sensitive data exposure.",
100+
"source": "secret",
101+
"tags": []
95102
}
96103
},
97104
"_problemRefs": {
98-
"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee": [
99-
"generic-api-key",
100-
"CWE-798"
101-
],
102-
"aaaaaaab-bbbb-cccc-dddd-eeeeeeeeeeee": [
103-
"generic-api-key-2",
104-
"CWE-798"
105-
]
105+
"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee": ["generic-api-key", "CWE-798"],
106+
"aaaaaaab-bbbb-cccc-dddd-eeeeeeeeeeee": ["generic-api-key-2", "CWE-798"],
107+
"399f629d-d638-441c-82a0-379e327dc941": ["generic-secret", "CWE-798"]
106108
},
107109
"findings": [
108110
{
@@ -170,6 +172,88 @@
170172
"links": {},
171173
"relationships": {},
172174
"type": "findings"
175+
},
176+
{
177+
"attributes": {
178+
"cause_of_failure": false,
179+
"description": "Do not hardcode passwords or other secrets directly in the source code. Use a secure secret management system instead.",
180+
"evidence": [],
181+
"finding_type": "secret",
182+
"key": "bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee",
183+
"locations": [
184+
{
185+
"file_path": "aws_keys.txt",
186+
"from_column": 35,
187+
"from_line": 4,
188+
"to_column": 75,
189+
"to_line": 4,
190+
"type": "source"
191+
}
192+
],
193+
"policy_modifications": [
194+
{
195+
"pointer": "/attributes/rating/severity",
196+
"reason": ""
197+
}
198+
],
199+
"problems": null,
200+
"rating": {
201+
"severity": "critical"
202+
},
203+
"risk": {},
204+
"suppression": {
205+
"created_at": "2026-02-05T15:38:05.824Z",
206+
"expires_at": "2027-09-09T00:00:00Z",
207+
"justification": "Someone is ignoring this",
208+
"policy": {
209+
"id": "90b2fe3f-f811-4447-8f75-e32545d753ea"
210+
},
211+
"status": "pending_ignore_approval"
212+
},
213+
"title": "Generic Secret"
214+
},
215+
"id": "399f629d-d638-441c-82a0-379e327dc941",
216+
"links": {},
217+
"relationships": {
218+
"policy": {
219+
"data": {
220+
"attributes": {
221+
"policies": [
222+
{
223+
"applied_policy": {
224+
"action_type": "ignore",
225+
"ignore": {
226+
"created": "2026-02-05T15:38:05.824Z",
227+
"expires": "2027-09-09T00:00:00Z",
228+
"ignored_by": {
229+
"email": "someone@snyk.io",
230+
"id": "00000000-0000-0000-0000-000000000000",
231+
"name": "Someone"
232+
},
233+
"reason": "Someone is ignoring this",
234+
"reason_type": "wont-fix",
235+
"source": ""
236+
},
237+
"rule": {
238+
"created": "0001-01-01T00:00:00Z",
239+
"id": "9a589a14-6ea4-49c5-9fd3-618dc3ebbbd1",
240+
"modified": "0001-01-01T00:00:00Z",
241+
"name": "",
242+
"review": "unknown"
243+
}
244+
},
245+
"id": "90b2fe3f-f811-4447-8f75-e32545d753ea",
246+
"type": "legacy_policy_snapshot"
247+
}
248+
]
249+
},
250+
"id": "91e77580-0b5d-4ae9-a7b2-4c035018857b",
251+
"type": "policies"
252+
},
253+
"links": {}
254+
}
255+
},
256+
"type": "findings"
173257
}
174258
]
175259
}

pkg/apiclients/testapi/issues.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ func (b *issueBuilder) deduplicate() {
806806
// build constructs the final Issue from collected data
807807
func (b *issueBuilder) build() *issue {
808808
metadata := b.buildMetadata()
809-
activeIgnoreDetail := determineActiveIgnoreDetail(b.ignoreDetails)
809+
ignoreDetail := determineIgnoreDetail(b.ignoreDetails)
810810

811811
return &issue{
812812
findings: b.findings,
@@ -826,23 +826,23 @@ func (b *issueBuilder) build() *issue {
826826
riskScore: b.riskScore,
827827
reachability: b.reachability,
828828
metadata: metadata,
829-
ignoreDetail: activeIgnoreDetail,
829+
ignoreDetail: ignoreDetail,
830830
}
831831
}
832832

833-
func determineActiveIgnoreDetail(ignoreDetails []IssueIgnoreDetails) IssueIgnoreDetails {
834-
activeIgnores := 0
835-
var firstIgnoreDetail IssueIgnoreDetails
836-
for _, detail := range ignoreDetails {
837-
if detail != nil && detail.IsActive() {
838-
firstIgnoreDetail = detail
839-
activeIgnores++
840-
}
833+
func determineIgnoreDetail(ignoreDetails []IssueIgnoreDetails) IssueIgnoreDetails {
834+
statusPriority := []SuppressionStatus{
835+
SuppressionStatusIgnored,
836+
SuppressionStatusPendingIgnoreApproval,
837+
SuppressionStatusOther,
841838
}
842839

843-
// if all findings are ignored, return the first ignore details
844-
if activeIgnores == len(ignoreDetails) {
845-
return firstIgnoreDetail
840+
for _, status := range statusPriority {
841+
for _, detail := range ignoreDetails {
842+
if detail != nil && detail.GetStatus() == status {
843+
return detail
844+
}
845+
}
846846
}
847847

848848
return nil

pkg/apiclients/testapi/issues_ignore_details_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,59 @@ func TestIssueIgnoreDetails_SuppressionFields(t *testing.T) {
171171
assert.Equal(t, skipIfFixable, *details.SkipIfFixable())
172172
}
173173

174+
func TestIssueIgnoreDetails_GetIgnoreDetails_BasedOnSuppressionStatus(t *testing.T) {
175+
testCases := []struct {
176+
name string
177+
suppressionStatuses []testapi.SuppressionStatus
178+
expectedStatus testapi.SuppressionStatus
179+
expectNilDetails bool
180+
}{
181+
{name: "single ignored", suppressionStatuses: []testapi.SuppressionStatus{testapi.SuppressionStatusIgnored}, expectedStatus: testapi.SuppressionStatusIgnored},
182+
{name: "single pending", suppressionStatuses: []testapi.SuppressionStatus{testapi.SuppressionStatusPendingIgnoreApproval}, expectedStatus: testapi.SuppressionStatusPendingIgnoreApproval},
183+
{name: "single other", suppressionStatuses: []testapi.SuppressionStatus{testapi.SuppressionStatusOther}, expectedStatus: testapi.SuppressionStatusOther},
184+
{name: "ignored wins over pending and other", suppressionStatuses: []testapi.SuppressionStatus{testapi.SuppressionStatusOther, testapi.SuppressionStatusPendingIgnoreApproval, testapi.SuppressionStatusIgnored}, expectedStatus: testapi.SuppressionStatusIgnored},
185+
{name: "pending wins over other", suppressionStatuses: []testapi.SuppressionStatus{testapi.SuppressionStatusOther, testapi.SuppressionStatusPendingIgnoreApproval}, expectedStatus: testapi.SuppressionStatusPendingIgnoreApproval},
186+
}
187+
188+
for _, tt := range testCases {
189+
t.Run(tt.name, func(t *testing.T) {
190+
findings := make([]*testapi.FindingData, len(tt.suppressionStatuses))
191+
for i, status := range tt.suppressionStatuses {
192+
findings[i] = &testapi.FindingData{
193+
Attributes: &testapi.FindingAttributes{Suppression: &testapi.Suppression{Status: status}},
194+
Id: func() *openapi_types.UUID { id := uuid.New(); return &id }(),
195+
}
196+
}
197+
198+
issue, err := testapi.NewIssueFromFindings(findings)
199+
require.NoError(t, err)
200+
201+
details := issue.GetIgnoreDetails()
202+
if tt.expectNilDetails {
203+
assert.Nil(t, details)
204+
} else {
205+
require.NotNil(t, details)
206+
assert.Equal(t, tt.expectedStatus, details.GetStatus())
207+
}
208+
})
209+
}
210+
211+
t.Run("empty suppression status yields nil", func(t *testing.T) {
212+
findings := []*testapi.FindingData{
213+
{
214+
Attributes: &testapi.FindingAttributes{Suppression: &testapi.Suppression{}},
215+
Id: func() *openapi_types.UUID { id := uuid.New(); return &id }(),
216+
},
217+
}
218+
219+
issue, err := testapi.NewIssueFromFindings(findings)
220+
require.NoError(t, err)
221+
222+
details := issue.GetIgnoreDetails()
223+
assert.Nil(t, details)
224+
})
225+
}
226+
174227
func TestIssueIgnoreDetails_IsActive(t *testing.T) {
175228
tests := []struct {
176229
name string
@@ -179,7 +232,7 @@ func TestIssueIgnoreDetails_IsActive(t *testing.T) {
179232
expectActive bool
180233
}{
181234
{"ignored status", testapi.SuppressionStatusIgnored, false, true},
182-
{"pending status", testapi.SuppressionStatusPendingIgnoreApproval, true, false},
235+
{"pending status", testapi.SuppressionStatusPendingIgnoreApproval, false, false},
183236
{"empty status", "", true, false},
184237
}
185238

0 commit comments

Comments
 (0)