Skip to content

Commit a6c6dc5

Browse files
authored
Add filter (severity, package type, accepted) to sysdig2sarif (#64)
1 parent 3f2cbd8 commit a6c6dc5

File tree

1 file changed

+128
-75
lines changed

1 file changed

+128
-75
lines changed

sarif/sysdig2sarif.go

Lines changed: 128 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,25 @@ package main
22

33
import (
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-
1312
type Report struct {
1413
Info Info `json:"info"`
1514
Result Result `json:"result"`
1615
}
17-
1816
type Info struct {
1917
ResultUrl string `json:"resultUrl"`
2018
ResultId string `json:"resultId"`
2119
}
22-
2320
type Result struct {
2421
Metadata Metadata `json:"metadata"`
2522
Packages []Package `json:"packages"`
2623
}
27-
2824
type 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-
3934
type 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-
4842
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"`
5449
}
55-
5650
type Severity struct {
5751
Value string `json:"value"`
5852
}
59-
6053
type CvssScore struct {
6154
Value CvssValue `json:"value"`
6255
}
63-
6456
type 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
7263
type SARIF struct {
7364
Schema string `json:"$schema"`
7465
Version string `json:"version"`
7566
Runs []SARIFRun `json:"runs"`
7667
}
77-
7868
type 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-
9685
type LogicalLocation struct {
9786
Name string `json:"name"`
9887
FullyQualifiedName string `json:"fullyQualifiedName"`
9988
Kind string `json:"kind"`
10089
}
101-
10290
type 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-
11299
type SARIFText struct {
113100
Text string `json:"text"`
114101
}
115-
116102
type SARIFHelp struct {
117103
Text string `json:"text"`
118104
Markdown string `json:"markdown"`
119105
}
120-
121106
type SARIFProperties struct {
122107
Precision string `json:"precision"`
123108
SecuritySeverity string `json:"security-severity"`
124109
Tags []string `json:"tags"`
125110
}
126-
127111
type SARIFResult struct {
128112
RuleId string `json:"ruleId"`
129113
Level string `json:"level"`
130114
Message SARIFText `json:"message"`
131115
Locations []SARIFLocation `json:"locations"`
132116
}
133-
134117
type SARIFLocation struct {
135118
PhysicalLocation SARIFPhysicalLocation `json:"physicalLocation"`
136119
Message SARIFText `json:"message"`
137120
}
138-
139121
type SARIFPhysicalLocation struct {
140122
ArtifactLocation SARIFArtifactLocation `json:"artifactLocation"`
141123
}
142-
143124
type 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
151206
func 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
166220
func getVulnFullDescription(pkg Package, vuln Vuln) string {
167221
return fmt.Sprintf("%s\nSeverity: %s\nPackage: %s\nType: %s\nFix: %s\nURL: 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
208225
func 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
256269
func 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
313324
func 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

Comments
 (0)