Skip to content

Commit

Permalink
Adds exit code functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
djschleen committed Nov 7, 2023
1 parent c0a5f5f commit 9139a54
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 16 deletions.
15 changes: 4 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,27 +174,20 @@ For example, the following command will return only high and critical vulnerabil
``` bash
bomber --severity=high scan bom.json
```
## Return codes
## Highest Severity Return Codes (Experimental)

Using the flag ```--exitcode```, will return with an exit code representing the highest vulnerability severity found. Without this flag you can expect an exit code of ```0``` for success, or ```1``` if an error was encountered.

Assuming there is no error, the following values will be returned by ```bomber``` when ```--exitcode```

| Option | Return Code |
| Severity | Return Code |
|---|---|
| NONE (default) | 0 |
| ERROR | 1 |
| UNSPECIFIED (This is a status where the provider gives us something wacky, or no info) | 10 |
| LOW | 11 |
| MODERATE | 12 |
| HIGH | 13 |
| CRITICAL | 14 |

0 is an "I don't care about filters" value and is the default (shows all output)
1 is that an error occurred
...
13 means at least 1 HIGH *or* "1" critical is found
14 means at least 1 CRITICAL is found.


## Data Enrichment

```bomber``` has the ability to enrich vulnerability data it obtains from the [Providers](#providers). The first "enricher" we have implemented for is for [EPSS](https://www.first.org/epss/)
Expand Down
12 changes: 7 additions & 5 deletions cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var (
provider models.Provider
ignoreFile string
severity string
exitCode bool

// summary, detailed bool
scanCmd = &cobra.Command{
Expand Down Expand Up @@ -142,16 +143,16 @@ var (
if err = renderer.Render(results); err != nil {
log.Println(err)
}
failSeverity := lib.ParseSeverity(severity)
if severity != "" {
log.Printf("fail severity: %d", failSeverity)
os.Exit(failSeverity)
if exitCode {
code := lib.HighestSeverityExitCode(lib.FlattenVulnerabilities(results.Packages))
log.Printf("fail severity: %d", code)
os.Exit(code)
}
log.Printf("Fail severity: %d", failSeverity)
} else {
util.PrintInfo("No packages were detected. Nothing has been scanned.")
}
log.Println("Finished")
os.Exit(0)
},
}
)
Expand All @@ -163,4 +164,5 @@ func init() {
scanCmd.PersistentFlags().StringVar(&providerName, "provider", "osv", "the vulnerability provider (ossindex, osv).")
scanCmd.PersistentFlags().StringVar(&ignoreFile, "ignore-file", "", "an optional file containing CVEs to ignore when rendering output.")
scanCmd.PersistentFlags().StringVar(&severity, "severity", "", "anything equal to or above this severity will be returned with non-zero error code.")
scanCmd.PersistentFlags().BoolVar(&exitCode, "exitcode", false, "if set will return an exit code representing the highest severity detected.")
}
32 changes: 32 additions & 0 deletions lib/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,35 @@ func ParseSeverity(severity string) int {
return 0
}
}

// HighestSeverityExitCode returns the exit code of the highest vulnerability
func HighestSeverityExitCode(vulnerabilities []models.Vulnerability) int {
severityExitCodes := map[string]int{
"UNDEFINED": int(models.UNDEFINED),
"LOW": int(models.LOW),
"MODERATE": int(models.MODERATE),
"HIGH": int(models.HIGH),
"CRITICAL": int(models.CRITICAL),
}

highestSeverity := "UNDEFINED" // Initialize with the lowest severity
for _, vulnerability := range vulnerabilities {
if exitCode, ok := severityExitCodes[vulnerability.Severity]; ok {
if exitCode > severityExitCodes[highestSeverity] {
highestSeverity = vulnerability.Severity
}
}
}

return severityExitCodes[highestSeverity]
}

func FlattenVulnerabilities(packages []models.Package) []models.Vulnerability {
var flattenedVulnerabilities []models.Vulnerability

for _, pkg := range packages {
flattenedVulnerabilities = append(flattenedVulnerabilities, pkg.Vulnerabilities...)
}

return flattenedVulnerabilities
}
52 changes: 52 additions & 0 deletions lib/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,55 @@ func TestParseSeverity(t *testing.T) {
assert.Equal(t, expected, result)
})
}

func TestHighestSeverityExitCode(t *testing.T) {
// Sample vulnerabilities with different severities
vulnerabilities := []models.Vulnerability{
{Severity: "LOW"},
{Severity: "CRITICAL"},
{Severity: "MODERATE"},
{Severity: "HIGH"},
{Severity: "UNDEFINED"},
}

// Calculate the expected exit code based on the highest severity
expectedExitCode := 14 // CRITICAL has the highest severity

// Call the function and check the result using assert
actualExitCode := HighestSeverityExitCode(vulnerabilities)
assert.Equal(t, expectedExitCode, actualExitCode)
}

func TestFlattenVulnerabilities(t *testing.T) {
// Create some sample data for testing
pkg1 := models.Package{
Purl: "pkg1",
Vulnerabilities: []models.Vulnerability{
{DisplayName: "Vuln1", Severity: "LOW"},
{DisplayName: "Vuln2", Severity: "HIGH"},
},
}

pkg2 := models.Package{
Purl: "pkg2",
Vulnerabilities: []models.Vulnerability{
{DisplayName: "Vuln3", Severity: "MODERATE"},
},
}

// Slice of Packages to test
packages := []models.Package{pkg1, pkg2}

// Call the FlattenVulnerabilities function
flattenedVulnerabilities := FlattenVulnerabilities(packages)

// Define the expected result
expectedVulnerabilities := []models.Vulnerability{
{DisplayName: "Vuln1", Severity: "LOW"},
{DisplayName: "Vuln2", Severity: "HIGH"},
{DisplayName: "Vuln3", Severity: "MODERATE"},
}

// Check if the actual result matches the expected result using assert.ElementsMatch
assert.ElementsMatch(t, expectedVulnerabilities, flattenedVulnerabilities)
}

0 comments on commit 9139a54

Please sign in to comment.