@@ -2,29 +2,25 @@ package main
2
2
3
3
import (
4
4
"encoding/json"
5
+ "flag"
5
6
"fmt"
6
7
"io/ioutil"
7
8
"os"
8
9
"strings"
9
10
)
10
11
11
- // ----------- Sysdig structs (simplified; adjust fields to match your actual JSON) -----------
12
-
13
12
type Report struct {
14
13
Info Info `json:"info"`
15
14
Result Result `json:"result"`
16
15
}
17
-
18
16
type Info struct {
19
17
ResultUrl string `json:"resultUrl"`
20
18
ResultId string `json:"resultId"`
21
19
}
22
-
23
20
type Result struct {
24
21
Metadata Metadata `json:"metadata"`
25
22
Packages []Package `json:"packages"`
26
23
}
27
-
28
24
type Metadata struct {
29
25
PullString string `json:"pullString"`
30
26
Digest string `json:"digest"`
@@ -35,7 +31,6 @@ type Metadata struct {
35
31
Size int `json:"size"`
36
32
LayersCount int `json:"layersCount"`
37
33
}
38
-
39
34
type Package struct {
40
35
Name string `json:"name"`
41
36
Version string `json:"version"`
@@ -44,37 +39,32 @@ type Package struct {
44
39
SuggestedFix string `json:"suggestedFix"`
45
40
Vulns []Vuln `json:"vulns"`
46
41
}
47
-
48
42
type Vuln struct {
49
- Name string `json:"name"`
50
- Severity Severity `json:"severity"`
51
- CvssScore CvssScore `json:"cvssScore"`
52
- Exploitable bool `json:"exploitable"`
53
- FixedInVersion string `json:"fixedInVersion"`
43
+ Name string `json:"name"`
44
+ Severity Severity `json:"severity"`
45
+ CvssScore CvssScore `json:"cvssScore"`
46
+ Exploitable bool `json:"exploitable"`
47
+ FixedInVersion string `json:"fixedInVersion"`
48
+ AcceptedRisks []interface {} `json:"acceptedRisks"`
54
49
}
55
-
56
50
type Severity struct {
57
51
Value string `json:"value"`
58
52
}
59
-
60
53
type CvssScore struct {
61
54
Value CvssValue `json:"value"`
62
55
}
63
-
64
56
type CvssValue struct {
65
57
Score float64 `json:"score"`
66
58
Version string `json:"version"`
67
59
Vector string `json:"vector"`
68
60
}
69
61
70
- // ----------- SARIF structs (essential subset) -----------
71
-
62
+ // SARIF structures
72
63
type SARIF struct {
73
64
Schema string `json:"$schema"`
74
65
Version string `json:"version"`
75
66
Runs []SARIFRun `json:"runs"`
76
67
}
77
-
78
68
type SARIFRun struct {
79
69
Tool struct {
80
70
Driver struct {
@@ -92,13 +82,11 @@ type SARIFRun struct {
92
82
ColumnKind string `json:"columnKind"`
93
83
Properties map [string ]interface {} `json:"properties"`
94
84
}
95
-
96
85
type LogicalLocation struct {
97
86
Name string `json:"name"`
98
87
FullyQualifiedName string `json:"fullyQualifiedName"`
99
88
Kind string `json:"kind"`
100
89
}
101
-
102
90
type SARIFRule struct {
103
91
Id string `json:"id"`
104
92
Name string `json:"name"`
@@ -108,46 +96,113 @@ type SARIFRule struct {
108
96
Help SARIFHelp `json:"help"`
109
97
Properties SARIFProperties `json:"properties"`
110
98
}
111
-
112
99
type SARIFText struct {
113
100
Text string `json:"text"`
114
101
}
115
-
116
102
type SARIFHelp struct {
117
103
Text string `json:"text"`
118
104
Markdown string `json:"markdown"`
119
105
}
120
-
121
106
type SARIFProperties struct {
122
107
Precision string `json:"precision"`
123
108
SecuritySeverity string `json:"security-severity"`
124
109
Tags []string `json:"tags"`
125
110
}
126
-
127
111
type SARIFResult struct {
128
112
RuleId string `json:"ruleId"`
129
113
Level string `json:"level"`
130
114
Message SARIFText `json:"message"`
131
115
Locations []SARIFLocation `json:"locations"`
132
116
}
133
-
134
117
type SARIFLocation struct {
135
118
PhysicalLocation SARIFPhysicalLocation `json:"physicalLocation"`
136
119
Message SARIFText `json:"message"`
137
120
}
138
-
139
121
type SARIFPhysicalLocation struct {
140
122
ArtifactLocation SARIFArtifactLocation `json:"artifactLocation"`
141
123
}
142
-
143
124
type SARIFArtifactLocation struct {
144
125
Uri string `json:"uri"`
145
126
UriBaseId string `json:"uriBaseId"`
146
127
}
147
128
148
- // ---------- Helpers ----------
129
+ // Filtering options
130
+ type FilterOptions struct {
131
+ MinSeverity string
132
+ PackageTypes []string
133
+ NotPackageTypes []string
134
+ ExcludeAccepted bool
135
+ }
136
+
137
+ // Severity order
138
+ var severityOrder = []string {"Negligible" , "Low" , "Medium" , "High" , "Critical" }
139
+
140
+ func isSeverityGte (a , b string ) bool {
141
+ var ai , bi int = - 1 , - 1
142
+ for i , v := range severityOrder {
143
+ if strings .EqualFold (v , a ) {
144
+ ai = i
145
+ }
146
+ if strings .EqualFold (v , b ) {
147
+ bi = i
148
+ }
149
+ }
150
+ return ai >= bi && ai >= 0 && bi >= 0
151
+ }
152
+
153
+ func splitAndTrim (s string ) []string {
154
+ parts := strings .Split (s , "," )
155
+ var out []string
156
+ for _ , v := range parts {
157
+ trimmed := strings .TrimSpace (v )
158
+ if trimmed != "" {
159
+ out = append (out , trimmed )
160
+ }
161
+ }
162
+ return out
163
+ }
164
+
165
+ func filterPackages (pkgs []Package , opts FilterOptions ) []Package {
166
+ var filtered []Package
167
+ TypeLoop:
168
+ for _ , pkg := range pkgs {
169
+ ptype := strings .ToLower (pkg .Type )
170
+ if len (opts .PackageTypes ) > 0 {
171
+ found := false
172
+ for _ , allowed := range opts .PackageTypes {
173
+ if strings .ToLower (allowed ) == ptype {
174
+ found = true
175
+ break
176
+ }
177
+ }
178
+ if ! found {
179
+ continue TypeLoop
180
+ }
181
+ }
182
+ for _ , notAllowed := range opts .NotPackageTypes {
183
+ if strings .ToLower (notAllowed ) == ptype {
184
+ continue TypeLoop
185
+ }
186
+ }
187
+ newVulns := []Vuln {}
188
+ for _ , vuln := range pkg .Vulns {
189
+ if opts .MinSeverity != "" && ! isSeverityGte (vuln .Severity .Value , opts .MinSeverity ) {
190
+ continue
191
+ }
192
+ if opts .ExcludeAccepted && len (vuln .AcceptedRisks ) > 0 {
193
+ fmt .Printf ("Accepted risks: %d\n " , len (vuln .AcceptedRisks ))
194
+ continue
195
+ }
196
+ newVulns = append (newVulns , vuln )
197
+ }
198
+ if len (newVulns ) > 0 {
199
+ pkg .Vulns = newVulns
200
+ filtered = append (filtered , pkg )
201
+ }
202
+ }
203
+ return filtered
204
+ }
149
205
150
- // Converts Sysdig severity string to SARIF level
151
206
func checkLevel (sev string ) string {
152
207
sev = strings .ToLower (sev )
153
208
switch sev {
@@ -162,59 +217,19 @@ func checkLevel(sev string) string {
162
217
}
163
218
}
164
219
165
- // Formats a vulnerability's full description for SARIF
166
220
func getVulnFullDescription (pkg Package , vuln Vuln ) string {
167
221
return fmt .Sprintf ("%s\n Severity: %s\n Package: %s\n Type: %s\n Fix: %s\n URL: https://nvd.nist.gov/vuln/detail/%s" ,
168
222
vuln .Name , vuln .Severity .Value , pkg .Name , pkg .Type , pkg .SuggestedFix , vuln .Name )
169
223
}
170
224
171
- // ---------- Main CLI Entry Point ----------
172
-
173
- func main () {
174
- if len (os .Args ) < 3 {
175
- fmt .Println ("Usage: sysdig2sarif input.json output.sarif [groupByPackage]" )
176
- os .Exit (1 )
177
- }
178
- inputFile := os .Args [1 ]
179
- outputFile := os .Args [2 ]
180
- groupByPackage := false
181
- if len (os .Args ) >= 4 && os .Args [3 ] == "true" {
182
- groupByPackage = true
183
- }
184
-
185
- raw , err := ioutil .ReadFile (inputFile )
186
- if err != nil {
187
- panic (err )
188
- }
189
- var data Report
190
- if err := json .Unmarshal (raw , & data ); err != nil {
191
- panic (err )
192
- }
193
-
194
- sarif := vulnerabilities2SARIF (data , groupByPackage )
195
- out , err := json .MarshalIndent (sarif , "" , " " )
196
- if err != nil {
197
- panic (err )
198
- }
199
- if err := ioutil .WriteFile (outputFile , out , 0644 ); err != nil {
200
- panic (err )
201
- }
202
- fmt .Println ("SARIF written to" , outputFile )
203
- }
204
-
205
- // ---------- Conversion Functions ----------
206
-
207
- // Converts the Sysdig report to a SARIF object
208
225
func vulnerabilities2SARIF (data Report , groupByPackage bool ) SARIF {
209
226
var rules []SARIFRule
210
227
var results []SARIFResult
211
-
212
228
if groupByPackage {
213
229
rules , results = vulnerabilities2SARIFResByPackage (data )
214
230
} else {
215
231
rules , results = vulnerabilities2SARIFRes (data )
216
232
}
217
-
218
233
run := SARIFRun {
219
234
LogicalLocations : []LogicalLocation {{
220
235
Name : "container-image" ,
@@ -236,11 +251,10 @@ func vulnerabilities2SARIF(data Report, groupByPackage bool) SARIF {
236
251
"resultId" : data .Info .ResultId ,
237
252
},
238
253
}
239
- // Fill in tool/driver info
240
254
run .Tool .Driver .Name = "sysdig-cli-scanner"
241
255
run .Tool .Driver .FullName = "Sysdig Vulnerability CLI Scanner"
242
256
run .Tool .Driver .InformationUri = "https://docs.sysdig.com/en/docs/installation/sysdig-secure/install-vulnerability-cli-scanner"
243
- run .Tool .Driver .Version = "1.0.0" // Change as appropriate
257
+ run .Tool .Driver .Version = "1.0.0"
244
258
run .Tool .Driver .SemanticVersion = "1.0.0"
245
259
run .Tool .Driver .DottedQuadFileVersion = "1.0.0.0"
246
260
run .Tool .Driver .Rules = rules
@@ -252,11 +266,9 @@ func vulnerabilities2SARIF(data Report, groupByPackage bool) SARIF {
252
266
}
253
267
}
254
268
255
- // SARIF conversion, grouping results by package
256
269
func vulnerabilities2SARIFResByPackage (data Report ) ([]SARIFRule , []SARIFResult ) {
257
270
var rules []SARIFRule
258
271
var results []SARIFResult
259
-
260
272
for _ , pkg := range data .Result .Packages {
261
273
if len (pkg .Vulns ) == 0 {
262
274
continue
@@ -309,12 +321,10 @@ func vulnerabilities2SARIFResByPackage(data Report) ([]SARIFRule, []SARIFResult)
309
321
return rules , results
310
322
}
311
323
312
- // SARIF conversion, result per vulnerability
313
324
func vulnerabilities2SARIFRes (data Report ) ([]SARIFRule , []SARIFResult ) {
314
325
var rules []SARIFRule
315
326
var results []SARIFResult
316
327
seen := map [string ]bool {}
317
-
318
328
for _ , pkg := range data .Result .Packages {
319
329
for _ , vuln := range pkg .Vulns {
320
330
if ! seen [vuln .Name ] {
@@ -355,3 +365,46 @@ func vulnerabilities2SARIFRes(data Report) ([]SARIFRule, []SARIFResult) {
355
365
}
356
366
return rules , results
357
367
}
368
+
369
+ func main () {
370
+ minSeverity := flag .String ("min-severity" , "" , "Minimum severity (e.g., High)" )
371
+ packageTypes := flag .String ("type" , "" , "Package types (comma-separated, e.g., java,javascript)" )
372
+ notPackageTypes := flag .String ("not-type" , "" , "Exclude package types (comma-separated)" )
373
+ excludeAccepted := flag .Bool ("exclude-accepted" , false , "Exclude vulnerabilities with accepted risks" )
374
+ groupByPackage := flag .Bool ("group-by-package" , false , "Group by package" )
375
+ flag .Parse ()
376
+
377
+ if flag .NArg () < 2 {
378
+ fmt .Println ("Usage: sysdig2sarif [flags] input.json output.sarif" )
379
+ os .Exit (1 )
380
+ }
381
+ inputFile := flag .Arg (0 )
382
+ outputFile := flag .Arg (1 )
383
+
384
+ raw , err := ioutil .ReadFile (inputFile )
385
+ if err != nil {
386
+ panic (err )
387
+ }
388
+ var data Report
389
+ if err := json .Unmarshal (raw , & data ); err != nil {
390
+ panic (err )
391
+ }
392
+
393
+ opts := FilterOptions {
394
+ MinSeverity : * minSeverity ,
395
+ PackageTypes : splitAndTrim (* packageTypes ),
396
+ NotPackageTypes : splitAndTrim (* notPackageTypes ),
397
+ ExcludeAccepted : * excludeAccepted ,
398
+ }
399
+ data .Result .Packages = filterPackages (data .Result .Packages , opts )
400
+
401
+ sarif := vulnerabilities2SARIF (data , * groupByPackage )
402
+ out , err := json .MarshalIndent (sarif , "" , " " )
403
+ if err != nil {
404
+ panic (err )
405
+ }
406
+ if err := ioutil .WriteFile (outputFile , out , 0644 ); err != nil {
407
+ panic (err )
408
+ }
409
+ fmt .Println ("SARIF written to" , outputFile )
410
+ }
0 commit comments