Skip to content

Commit 41cf7de

Browse files
committed
misc bug fixes and improvements to validate output
These "improvements" are short term hacks. There is a need for a fundamental overhaul of how output is generated to improve usability
1 parent 4d2dd80 commit 41cf7de

File tree

3 files changed

+96
-5
lines changed

3 files changed

+96
-5
lines changed

cmd/cli-config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ var RuntimeConfig = metaConfig{
165165
CobraSetupFunc: func(f configkit.MetaField, cmd *cobra.Command) {
166166
valueP := f.FlagValueP.(*string)
167167
usage := f.Metadata[metadataFlagUsage]
168-
cmd.PersistentFlags().StringVarP(valueP, "file", "f", "", usage)
168+
cmd.PersistentFlags().StringVarP(valueP, "config", "f", "", usage)
169169
},
170170
Metadata: map[string]string{
171171
metadataFlagUsage: "a validation configuration file",

cmd/validate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ var validateCmd = &cobra.Command{
6969
return nil
7070
}
7171

72-
return nil
72+
return err
7373
},
7474
}
7575

pkg/gatecheck/validate.go

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"log/slog"
1111
"slices"
12+
"sort"
1213
"strings"
1314

1415
"github.com/gatecheckdev/gatecheck/pkg/archive"
@@ -65,6 +66,34 @@ func Validate(config *Config, reportSrc io.Reader, targetFilename string, option
6566
}
6667
}
6768

69+
func removeIgnoredSeverityCVEs(config *Config, report *artifacts.GrypeReportMin, data *epss.Data) {
70+
hasLimits := map[string]bool{
71+
"critical": config.Grype.SeverityLimit.Critical.Enabled,
72+
"high": config.Grype.SeverityLimit.High.Enabled,
73+
"medium": config.Grype.SeverityLimit.Medium.Enabled,
74+
"low": config.Grype.SeverityLimit.Low.Enabled,
75+
"unknown": false,
76+
"negligible": false,
77+
}
78+
79+
for severity, hasLimit := range hasLimits {
80+
if hasLimit {
81+
continue
82+
}
83+
84+
if config.Grype.EPSSLimit.Enabled {
85+
report.Matches = slices.DeleteFunc(report.Matches, func(match artifacts.GrypeMatch) bool {
86+
epssCVE, ok := data.CVEs[match.Vulnerability.ID]
87+
return strings.ToLower(match.Vulnerability.Severity) == severity && (!ok || epssCVE.EPSSValue() < config.Grype.EPSSLimit.Score)
88+
})
89+
} else {
90+
report.Matches = slices.DeleteFunc(report.Matches, func(match artifacts.GrypeMatch) bool {
91+
return strings.ToLower(match.Vulnerability.Severity) == severity
92+
})
93+
}
94+
}
95+
}
96+
6897
func ruleGrypeSeverityLimit(config *Config, report *artifacts.GrypeReportMin) bool {
6998
validationPass := true
7099

@@ -86,6 +115,9 @@ func ruleGrypeSeverityLimit(config *Config, report *artifacts.GrypeReportMin) bo
86115
}
87116
if matchCount > int(configuredLimit.Limit) {
88117
slog.Error("grype severity limit exceeded", "severity", severity, "report", matchCount, "limit", configuredLimit.Limit)
118+
for _, match := range matches {
119+
slog.Info("vulnerability detected", "id", match.Vulnerability.ID, "severity", match.Vulnerability.Severity)
120+
}
89121
validationPass = false
90122
continue
91123
}
@@ -359,7 +391,7 @@ func ruleGrypeEPSSLimit(config *Config, report *artifacts.GrypeReportMin, data *
359391
slog.Debug("run epss limit rule",
360392
"artifact", "grype",
361393
"vulnerabilities", len(report.Matches),
362-
"epss_risk_acceptance_score", config.Grype.EPSSRiskAcceptance.Score,
394+
"epss_limit_score", config.Grype.EPSSLimit.Score,
363395
)
364396
for _, match := range report.Matches {
365397
epssCVE, ok := data.CVEs[match.Vulnerability.ID]
@@ -402,7 +434,7 @@ func ruleCyclonedxEPSSLimit(config *Config, report *artifacts.CyclonedxReportMin
402434
slog.Debug("run epss limit rule",
403435
"artifact", "cyclonedx",
404436
"vulnerabilities", len(report.Vulnerabilities),
405-
"epss_risk_acceptance_score", config.Cyclonedx.EPSSRiskAcceptance.Score,
437+
"epss_limit_score", config.Cyclonedx.EPSSLimit.Score,
406438
)
407439

408440
for _, vulnerability := range report.Vulnerabilities {
@@ -431,6 +463,24 @@ func ruleCyclonedxEPSSLimit(config *Config, report *artifacts.CyclonedxReportMin
431463
return true
432464
}
433465

466+
func removeIgnoredSemgrepIssues(config *Config, report *artifacts.SemgrepReportMin) {
467+
hasLimits := map[string]bool{
468+
"error": config.Semgrep.SeverityLimit.Error.Enabled,
469+
"warning": config.Semgrep.SeverityLimit.Warning.Enabled,
470+
"info": config.Semgrep.SeverityLimit.Info.Enabled,
471+
}
472+
473+
for severity, hasLimit := range hasLimits {
474+
if hasLimit {
475+
continue
476+
}
477+
478+
report.Results = slices.DeleteFunc(report.Results, func(result artifacts.SemgrepResults) bool {
479+
return strings.EqualFold(result.Extra.Severity, severity)
480+
})
481+
}
482+
}
483+
434484
func ruleSemgrepSeverityLimit(config *Config, report *artifacts.SemgrepReportMin) bool {
435485
slog.Debug(
436486
"severity limit rule", "artifact", "semgrep",
@@ -458,6 +508,9 @@ func ruleSemgrepSeverityLimit(config *Config, report *artifacts.SemgrepReportMin
458508
}
459509
if matchCount > int(configuredLimit.Limit) {
460510
slog.Error("severity limit exceeded", "artifact", "semgrep", "severity", severity, "report", matchCount, "limit", configuredLimit.Limit)
511+
for _, match := range matches {
512+
slog.Info("Potential issue detected", "severity", match.Extra.Severity, "check_id", match.CheckID, "message", match.Extra.Message)
513+
}
461514
validationPass = false
462515
continue
463516
}
@@ -483,6 +536,7 @@ func ruleSemgrepImpactRiskAccept(config *Config, report *artifacts.SemgrepReport
483536

484537
results := slices.DeleteFunc(report.Results, func(result artifacts.SemgrepResults) bool {
485538
riskAccepted := false
539+
// TODO: make the configuration for risk acceptance less dumb (what would you accept high medium impact and not accept low impact)
486540
switch {
487541
case config.Semgrep.ImpactRiskAcceptance.High && strings.EqualFold(result.Extra.Metadata.Impact, "high"):
488542
riskAccepted = true
@@ -494,7 +548,7 @@ func ruleSemgrepImpactRiskAccept(config *Config, report *artifacts.SemgrepReport
494548

495549
if riskAccepted {
496550
slog.Info(
497-
"risk accepted: epss score is below risk acceptance threshold",
551+
"risk accepted: Semgrep issue impact is below acceptance threshold",
498552
"check_id", result.CheckID,
499553
"severity", result.Extra.Severity,
500554
"impact", result.Extra.Metadata.Impact,
@@ -647,19 +701,29 @@ func validateCoverage(src io.Reader, targetFilename string, config *Config) erro
647701
functionCoverage := float32(report.CoveredFunctions) / float32(report.TotalFunctions)
648702
branchCoverage := float32(report.CoveredBranches) / float32(report.TotalBranches)
649703

704+
slog.Info(
705+
"validate coverage",
706+
"line_coverage", lineCoverage,
707+
"function_coverage", functionCoverage,
708+
"branch_coverage", branchCoverage,
709+
)
710+
650711
var errs error
651712

652713
if lineCoverage < config.Coverage.LineThreshold {
714+
slog.Error("line coverage below threshold", "line_coverage", lineCoverage, "threshold", config.Coverage.LineThreshold)
653715
coverageErr := newValidationErr("Coverage: Line coverage below threshold")
654716
errs = errors.Join(errs, coverageErr)
655717
}
656718

657719
if functionCoverage < config.Coverage.FunctionThreshold {
720+
slog.Error("function coverage below threshold", "function_coverage", functionCoverage, "threshold", config.Coverage.FunctionThreshold)
658721
coverageErr := newValidationErr("Coverage: Function coverage below threshold")
659722
errs = errors.Join(errs, coverageErr)
660723
}
661724

662725
if branchCoverage < config.Coverage.BranchThreshold {
726+
slog.Error("branch coverage below threshold", "branch_coverage", branchCoverage, "threshold", config.Coverage.BranchThreshold)
663727
coverageErr := newValidationErr("Coverage: Branch coverage below threshold")
664728
errs = errors.Join(errs, coverageErr)
665729
}
@@ -713,11 +777,34 @@ func validateBundle(r io.Reader, config *Config, options *fetchOptions) error {
713777
// Validate Rules
714778

715779
func validateGrypeRules(config *Config, report *artifacts.GrypeReportMin, catalog *kev.Catalog, data *epss.Data) error {
780+
severityRank := []string{
781+
"critical",
782+
"high",
783+
"medium",
784+
"low",
785+
"negligible",
786+
"unknown",
787+
}
788+
sort.Slice(report.Matches, func(i, j int) bool {
789+
if report.Matches[i].Vulnerability.Severity == report.Matches[j].Vulnerability.Severity {
790+
epssi, oki := data.CVEs[report.Matches[i].Vulnerability.ID]
791+
epssj, okj := data.CVEs[report.Matches[j].Vulnerability.ID]
792+
793+
// Sort EPPS from highest to lowest
794+
return !okj || oki && epssi.EPSSValue() > epssj.EPSSValue()
795+
}
796+
ranki := slices.Index(severityRank, strings.ToLower(report.Matches[i].Vulnerability.Severity))
797+
rankj := slices.Index(severityRank, strings.ToLower(report.Matches[j].Vulnerability.Severity))
798+
return ranki < rankj
799+
})
716800
// 1. Deny List - Fail Matching
717801
if !ruleGrypeCVEDeny(config, report) {
718802
return newValidationErr("Grype: CVE explicitly denied")
719803
}
720804

805+
// Ignore any CVEs that don't meet the vulnerability threshold or the EPPS threshold
806+
removeIgnoredSeverityCVEs(config, report, data)
807+
721808
// 2. CVE Allowance - remove from matches
722809
ruleGrypeCVEAllow(config, report)
723810

@@ -773,6 +860,10 @@ func validateCyclonedxRules(config *Config, report *artifacts.CyclonedxReportMin
773860
}
774861

775862
func validateSemgrepRules(config *Config, report *artifacts.SemgrepReportMin) error {
863+
slog.Info("validating semgrep rules", "findings", len(report.Results))
864+
// Ignore issues for which there is no severity limit
865+
removeIgnoredSemgrepIssues(config, report)
866+
776867
// 1. Impact Allowance - remove result
777868
ruleSemgrepImpactRiskAccept(config, report)
778869

0 commit comments

Comments
 (0)