@@ -2,29 +2,25 @@ package main
22
33import (
44 "encoding/json"
5+ "flag"
56 "fmt"
67 "io/ioutil"
78 "os"
89 "strings"
910)
1011
11- // ----------- Sysdig structs (simplified; adjust fields to match your actual JSON) -----------
12-
1312type Report struct {
1413 Info Info `json:"info"`
1514 Result Result `json:"result"`
1615}
17-
1816type Info struct {
1917 ResultUrl string `json:"resultUrl"`
2018 ResultId string `json:"resultId"`
2119}
22-
2320type Result struct {
2421 Metadata Metadata `json:"metadata"`
2522 Packages []Package `json:"packages"`
2623}
27-
2824type Metadata struct {
2925 PullString string `json:"pullString"`
3026 Digest string `json:"digest"`
@@ -35,7 +31,6 @@ type Metadata struct {
3531 Size int `json:"size"`
3632 LayersCount int `json:"layersCount"`
3733}
38-
3934type Package struct {
4035 Name string `json:"name"`
4136 Version string `json:"version"`
@@ -44,37 +39,32 @@ type Package struct {
4439 SuggestedFix string `json:"suggestedFix"`
4540 Vulns []Vuln `json:"vulns"`
4641}
47-
4842type 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"`
5449}
55-
5650type Severity struct {
5751 Value string `json:"value"`
5852}
59-
6053type CvssScore struct {
6154 Value CvssValue `json:"value"`
6255}
63-
6456type CvssValue struct {
6557 Score float64 `json:"score"`
6658 Version string `json:"version"`
6759 Vector string `json:"vector"`
6860}
6961
70- // ----------- SARIF structs (essential subset) -----------
71-
62+ // SARIF structures
7263type SARIF struct {
7364 Schema string `json:"$schema"`
7465 Version string `json:"version"`
7566 Runs []SARIFRun `json:"runs"`
7667}
77-
7868type SARIFRun struct {
7969 Tool struct {
8070 Driver struct {
@@ -92,13 +82,11 @@ type SARIFRun struct {
9282 ColumnKind string `json:"columnKind"`
9383 Properties map [string ]interface {} `json:"properties"`
9484}
95-
9685type LogicalLocation struct {
9786 Name string `json:"name"`
9887 FullyQualifiedName string `json:"fullyQualifiedName"`
9988 Kind string `json:"kind"`
10089}
101-
10290type SARIFRule struct {
10391 Id string `json:"id"`
10492 Name string `json:"name"`
@@ -108,46 +96,113 @@ type SARIFRule struct {
10896 Help SARIFHelp `json:"help"`
10997 Properties SARIFProperties `json:"properties"`
11098}
111-
11299type SARIFText struct {
113100 Text string `json:"text"`
114101}
115-
116102type SARIFHelp struct {
117103 Text string `json:"text"`
118104 Markdown string `json:"markdown"`
119105}
120-
121106type SARIFProperties struct {
122107 Precision string `json:"precision"`
123108 SecuritySeverity string `json:"security-severity"`
124109 Tags []string `json:"tags"`
125110}
126-
127111type SARIFResult struct {
128112 RuleId string `json:"ruleId"`
129113 Level string `json:"level"`
130114 Message SARIFText `json:"message"`
131115 Locations []SARIFLocation `json:"locations"`
132116}
133-
134117type SARIFLocation struct {
135118 PhysicalLocation SARIFPhysicalLocation `json:"physicalLocation"`
136119 Message SARIFText `json:"message"`
137120}
138-
139121type SARIFPhysicalLocation struct {
140122 ArtifactLocation SARIFArtifactLocation `json:"artifactLocation"`
141123}
142-
143124type SARIFArtifactLocation struct {
144125 Uri string `json:"uri"`
145126 UriBaseId string `json:"uriBaseId"`
146127}
147128
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+ }
149205
150- // Converts Sysdig severity string to SARIF level
151206func checkLevel (sev string ) string {
152207 sev = strings .ToLower (sev )
153208 switch sev {
@@ -162,59 +217,19 @@ func checkLevel(sev string) string {
162217 }
163218}
164219
165- // Formats a vulnerability's full description for SARIF
166220func getVulnFullDescription (pkg Package , vuln Vuln ) string {
167221 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" ,
168222 vuln .Name , vuln .Severity .Value , pkg .Name , pkg .Type , pkg .SuggestedFix , vuln .Name )
169223}
170224
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
208225func vulnerabilities2SARIF (data Report , groupByPackage bool ) SARIF {
209226 var rules []SARIFRule
210227 var results []SARIFResult
211-
212228 if groupByPackage {
213229 rules , results = vulnerabilities2SARIFResByPackage (data )
214230 } else {
215231 rules , results = vulnerabilities2SARIFRes (data )
216232 }
217-
218233 run := SARIFRun {
219234 LogicalLocations : []LogicalLocation {{
220235 Name : "container-image" ,
@@ -236,11 +251,10 @@ func vulnerabilities2SARIF(data Report, groupByPackage bool) SARIF {
236251 "resultId" : data .Info .ResultId ,
237252 },
238253 }
239- // Fill in tool/driver info
240254 run .Tool .Driver .Name = "sysdig-cli-scanner"
241255 run .Tool .Driver .FullName = "Sysdig Vulnerability CLI Scanner"
242256 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"
244258 run .Tool .Driver .SemanticVersion = "1.0.0"
245259 run .Tool .Driver .DottedQuadFileVersion = "1.0.0.0"
246260 run .Tool .Driver .Rules = rules
@@ -252,11 +266,9 @@ func vulnerabilities2SARIF(data Report, groupByPackage bool) SARIF {
252266 }
253267}
254268
255- // SARIF conversion, grouping results by package
256269func vulnerabilities2SARIFResByPackage (data Report ) ([]SARIFRule , []SARIFResult ) {
257270 var rules []SARIFRule
258271 var results []SARIFResult
259-
260272 for _ , pkg := range data .Result .Packages {
261273 if len (pkg .Vulns ) == 0 {
262274 continue
@@ -309,12 +321,10 @@ func vulnerabilities2SARIFResByPackage(data Report) ([]SARIFRule, []SARIFResult)
309321 return rules , results
310322}
311323
312- // SARIF conversion, result per vulnerability
313324func vulnerabilities2SARIFRes (data Report ) ([]SARIFRule , []SARIFResult ) {
314325 var rules []SARIFRule
315326 var results []SARIFResult
316327 seen := map [string ]bool {}
317-
318328 for _ , pkg := range data .Result .Packages {
319329 for _ , vuln := range pkg .Vulns {
320330 if ! seen [vuln .Name ] {
@@ -355,3 +365,46 @@ func vulnerabilities2SARIFRes(data Report) ([]SARIFRule, []SARIFResult) {
355365 }
356366 return rules , results
357367}
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