diff --git a/cli/docs/flags.go b/cli/docs/flags.go index 6ac7f619b..8822ea80e 100644 --- a/cli/docs/flags.go +++ b/cli/docs/flags.go @@ -2,9 +2,10 @@ package docs import ( "fmt" - "github.com/jfrog/jfrog-cli-security/commands/git" "strings" + "github.com/jfrog/jfrog-cli-security/commands/git" + "github.com/jfrog/jfrog-cli-core/v2/common/cliutils" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" @@ -88,25 +89,26 @@ const ( Periodic = "periodic" // Unique scan and audit flags - scanPrefix = "scan-" - scanRecursive = scanPrefix + Recursive - scanRegexp = scanPrefix + RegexpFlag - scanAnt = scanPrefix + AntFlag - OutputFormat = "format" - BypassArchiveLimits = "bypass-archive-limits" - Watches = "watches" - RepoPath = "repo-path" - Licenses = "licenses" - Fail = "fail" - ExtendedTable = "extended-table" - MinSeverity = "min-severity" - FixableOnly = "fixable-only" - Rescan = "rescan" - Vuln = "vuln" - buildPrefix = "build-" - BuildVuln = buildPrefix + Vuln - ScanVuln = scanPrefix + Vuln - SecretValidation = "validate-secrets" + scanPrefix = "scan-" + scanRecursive = scanPrefix + Recursive + scanRegexp = scanPrefix + RegexpFlag + scanAnt = scanPrefix + AntFlag + OutputFormat = "format" + BypassArchiveLimits = "bypass-archive-limits" + Watches = "watches" + RepoPath = "repo-path" + Licenses = "licenses" + Fail = "fail" + ExtendedTable = "extended-table" + MinSeverity = "min-severity" + FixableOnly = "fixable-only" + SkipNotApplicableCves = "skip-not-applicable-cves" + Rescan = "rescan" + Vuln = "vuln" + buildPrefix = "build-" + BuildVuln = buildPrefix + Vuln + ScanVuln = scanPrefix + Vuln + SecretValidation = "validate-secrets" // Unique audit flags auditPrefix = "audit-" @@ -150,13 +152,13 @@ var commandFlags = map[string][]string{ url, user, password, accessToken, ServerId, Project, BuildVuln, OutputFormat, Fail, ExtendedTable, Rescan, }, DockerScan: { - ServerId, Project, Watches, RepoPath, Licenses, OutputFormat, Fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, ScanVuln, SecretValidation, + ServerId, Project, Watches, RepoPath, Licenses, OutputFormat, Fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, ScanVuln, SecretValidation, SkipNotApplicableCves, }, Audit: { url, user, password, accessToken, ServerId, InsecureTls, Project, Watches, RepoPath, Licenses, OutputFormat, ExcludeTestDeps, useWrapperAudit, DepType, RequirementsFile, Fail, ExtendedTable, WorkingDirs, ExclusionsAudit, Mvn, Gradle, Npm, Pnpm, Yarn, Go, Nuget, Pip, Pipenv, Poetry, MinSeverity, FixableOnly, ThirdPartyContextualAnalysis, Threads, - Sca, Iac, Sast, Secrets, WithoutCA, ScanVuln, SecretValidation, OutputDir, SkipAutoInstall, AllowPartialResults, + Sca, Iac, Sast, Secrets, WithoutCA, ScanVuln, SecretValidation, OutputDir, SkipAutoInstall, AllowPartialResults, SkipNotApplicableCves, }, CurationAudit: { CurationOutput, WorkingDirs, Threads, RequirementsFile, @@ -216,16 +218,17 @@ var flagsMap = map[string]components.Flag{ "Defines the output format of the command. Acceptable values are: table, json, simple-json and sarif. Note: the json format doesn't include information about scans that are included as part of the Advanced Security package.", components.WithStrDefaultValue("table"), ), - Fail: components.NewBoolFlag(Fail, fmt.Sprintf("When using one of the flags --%s, --%s or --%s and a 'Fail build' rule is matched, the command will return exit code 3. Set to false if you'd like to see violations with exit code 0.", Watches, Project, RepoPath), components.WithBoolDefaultValue(true)), - ExtendedTable: components.NewBoolFlag(ExtendedTable, "Set to true if you'd like the table to include extended fields such as 'CVSS' & 'Xray Issue Id'. Ignored if provided 'format' is not 'table'."), - BypassArchiveLimits: components.NewBoolFlag(BypassArchiveLimits, "Set to true to bypass the indexer-app archive limits."), - MinSeverity: components.NewStringFlag(MinSeverity, "Set the minimum severity of issues to display. The following values are accepted: Low, Medium, High or Critical."), - FixableOnly: components.NewBoolFlag(FixableOnly, "Set to true if you wish to display issues that have a fixed version only."), - Rescan: components.NewBoolFlag(Rescan, "Set to true when scanning an already successfully scanned build, for example after adding an ignore rule."), - BuildVuln: components.NewBoolFlag(Vuln, "Set to true if you'd like to receive an additional view of all vulnerabilities, regardless of the policy configured in Xray. Ignored if provided 'format' is 'sarif'."), - ScanVuln: components.NewBoolFlag(Vuln, "Set to true if you'd like to receive an additional view of all vulnerabilities, regardless of the policy configured in Xray."), - InsecureTls: components.NewBoolFlag(InsecureTls, "Set to true to skip TLS certificates verification."), - ExcludeTestDeps: components.NewBoolFlag(ExcludeTestDeps, "[Gradle] Set to true if you'd like to exclude Gradle test dependencies from Xray scanning."), + Fail: components.NewBoolFlag(Fail, fmt.Sprintf("When using one of the flags --%s, --%s or --%s and a 'Fail build' rule is matched, the command will return exit code 3. Set to false if you'd like to see violations with exit code 0.", Watches, Project, RepoPath), components.WithBoolDefaultValue(true)), + ExtendedTable: components.NewBoolFlag(ExtendedTable, "Set to true if you'd like the table to include extended fields such as 'CVSS' & 'Xray Issue Id'. Ignored if provided 'format' is not 'table'."), + BypassArchiveLimits: components.NewBoolFlag(BypassArchiveLimits, "Set to true to bypass the indexer-app archive limits."), + MinSeverity: components.NewStringFlag(MinSeverity, "Set the minimum severity of issues to display. The following values are accepted: Low, Medium, High or Critical."), + FixableOnly: components.NewBoolFlag(FixableOnly, "Set to true if you wish to display issues that have a fixed version only."), + SkipNotApplicableCves: components.NewBoolFlag(SkipNotApplicableCves, "Set to true if you wish to not display issues with Not Applicable CVEs"), + Rescan: components.NewBoolFlag(Rescan, "Set to true when scanning an already successfully scanned build, for example after adding an ignore rule."), + BuildVuln: components.NewBoolFlag(Vuln, "Set to true if you'd like to receive an additional view of all vulnerabilities, regardless of the policy configured in Xray. Ignored if provided 'format' is 'sarif'."), + ScanVuln: components.NewBoolFlag(Vuln, "Set to true if you'd like to receive an additional view of all vulnerabilities, regardless of the policy configured in Xray."), + InsecureTls: components.NewBoolFlag(InsecureTls, "Set to true to skip TLS certificates verification."), + ExcludeTestDeps: components.NewBoolFlag(ExcludeTestDeps, "[Gradle] Set to true if you'd like to exclude Gradle test dependencies from Xray scanning."), useWrapperAudit: components.NewBoolFlag( UseWrapper, "Set to false if you wish to not use the gradle or maven wrapper.", diff --git a/cli/scancommands.go b/cli/scancommands.go index 65569d63a..5fba864f8 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -3,6 +3,9 @@ package cli import ( "errors" "fmt" + "os" + "strings" + buildInfoUtils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/common/cliutils" @@ -22,8 +25,6 @@ import ( "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/urfave/cli" - "os" - "strings" flags "github.com/jfrog/jfrog-cli-security/cli/docs" auditSpecificDocs "github.com/jfrog/jfrog-cli-security/cli/docs/auditspecific" @@ -250,6 +251,7 @@ func ScanCmd(c *components.Context) error { SetPrintExtendedTable(c.GetBoolFlagValue(flags.ExtendedTable)). SetBypassArchiveLimits(c.GetBoolFlagValue(flags.BypassArchiveLimits)). SetFixableOnly(c.GetBoolFlagValue(flags.FixableOnly)). + SetSkipNotApplicableCves(c.GetBoolFlagValue(flags.SkipNotApplicableCves)). SetMinSeverityFilter(minSeverity) if c.IsFlagSet(flags.Watches) { scanCmd.SetWatches(splitByCommaAndTrim(c.GetStringFlagValue(flags.Watches))) @@ -478,6 +480,7 @@ func CreateAuditCmd(c *components.Context) (string, string, *coreConfig.ServerDe SetPrintExtendedTable(c.GetBoolFlagValue(flags.ExtendedTable)). SetMinSeverityFilter(minSeverity). SetFixableOnly(c.GetBoolFlagValue(flags.FixableOnly)). + SetSkipNotApplicableCves(c.GetBoolFlagValue(flags.SkipNotApplicableCves)). SetThirdPartyApplicabilityScan(c.GetBoolFlagValue(flags.ThirdPartyContextualAnalysis)). SetScansResultsOutputDir(scansOutputDir). SetSkipAutoInstall(c.GetBoolFlagValue(flags.SkipAutoInstall)). @@ -739,6 +742,7 @@ func DockerScan(c *components.Context, image string) error { SetPrintExtendedTable(c.GetBoolFlagValue(flags.ExtendedTable)). SetBypassArchiveLimits(c.GetBoolFlagValue(flags.BypassArchiveLimits)). SetFixableOnly(c.GetBoolFlagValue(flags.FixableOnly)). + SetSkipNotApplicableCves(c.GetBoolFlagValue(flags.SkipNotApplicableCves)). SetMinSeverityFilter(minSeverity). SetThreads(threads). SetSecretValidation(c.GetBoolFlagValue(flags.SecretValidation)) diff --git a/commands/audit/audit.go b/commands/audit/audit.go index 5d347f5f6..d616aa2b5 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -123,6 +123,7 @@ func (auditCmd *AuditCommand) Run() (err error) { SetWorkingDirs(workingDirs). SetMinSeverityFilter(auditCmd.minSeverityFilter). SetFixableOnly(auditCmd.fixableOnly). + SetSkipNotApplicableCves(auditCmd.skipNotApplicableCves). SetGraphBasicParams(auditCmd.AuditBasicParams). SetCommonGraphScanParams(auditCmd.CreateCommonGraphScanParams()). SetThirdPartyApplicabilityScan(auditCmd.thirdPartyApplicabilityScan). @@ -212,6 +213,11 @@ func RunAudit(auditParams *AuditParams) (cmdResults *results.SecurityCommandResu auditParallelRunner.Runner.Done() }() auditParallelRunner.Runner.Run() + + /*Filter out non-applicative results after getting the applicability results*/ + if auditParams.skipNotApplicableCves { + jas.FilterSkipNotApplicable(cmdResults) + } return } diff --git a/commands/audit/auditparams.go b/commands/audit/auditparams.go index 595c9673f..ed7e47995 100644 --- a/commands/audit/auditparams.go +++ b/commands/audit/auditparams.go @@ -16,6 +16,7 @@ type AuditParams struct { workingDirs []string installFunc func(tech string) error fixableOnly bool + skipNotApplicableCves bool minSeverityFilter severityutils.Severity *xrayutils.AuditBasicParams multiScanId string @@ -83,6 +84,15 @@ func (params *AuditParams) SetFixableOnly(fixable bool) *AuditParams { return params } +func (params *AuditParams) SkipNotApplicableCves() bool { + return params.skipNotApplicableCves +} + +func (params *AuditParams) SetSkipNotApplicableCves(skip bool) *AuditParams { + params.skipNotApplicableCves = skip + return params +} + func (params *AuditParams) MinSeverityFilter() severityutils.Severity { return params.minSeverityFilter } diff --git a/commands/scan/scan.go b/commands/scan/scan.go index 23b24f970..adb7055b0 100644 --- a/commands/scan/scan.go +++ b/commands/scan/scan.go @@ -71,6 +71,7 @@ type ScanCommand struct { validateSecrets bool bypassArchiveLimits bool fixableOnly bool + skipNotApplicableCves bool progress ioUtils.ProgressMgr // JAS is only supported for Docker images. commandSupportsJAS bool @@ -97,6 +98,11 @@ func (scanCmd *ScanCommand) SetFixableOnly(fixable bool) *ScanCommand { return scanCmd } +func (scanCmd *ScanCommand) SetSkipNotApplicableCves(skip bool) *ScanCommand { + scanCmd.skipNotApplicableCves = skip + return scanCmd +} + func (scanCmd *ScanCommand) SetRunJasScans(run bool) *ScanCommand { scanCmd.commandSupportsJAS = run return scanCmd @@ -248,6 +254,10 @@ func (scanCmd *ScanCommand) RunAndRecordResults(cmdType utils.CommandType, recor } } + if scanCmd.skipNotApplicableCves { + jas.FilterSkipNotApplicable(cmdResults) + } + if err = output.NewResultsWriter(cmdResults). SetOutputFormat(scanCmd.outputFormat). SetHasViolationContext(scanCmd.hasViolationContext()). @@ -463,6 +473,7 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults SetXrayGraphScanParams(params). SetXrayVersion(cmdResults.XrayVersion). SetFixableOnly(scanCmd.fixableOnly). + SetSkipNotApplicableCves(scanCmd.skipNotApplicableCves). SetSeverityLevel(scanCmd.minSeverityFilter.String()) xrayManager, err := xray.CreateXrayServiceManager(scanGraphParams.ServerDetails()) if err != nil { diff --git a/jas/applicability/applicabilitymanager.go b/jas/applicability/applicabilitymanager.go index 5e346bead..836b96f41 100644 --- a/jas/applicability/applicabilitymanager.go +++ b/jas/applicability/applicabilitymanager.go @@ -65,6 +65,7 @@ func RunApplicabilityScan(xrayResults []services.ScanResponse, directDependencie return } results = applicabilityScanManager.applicabilityScanResults + if len(results) > 0 { log.Info(clientutils.GetLogMsgPrefix(threadId, false)+"Found", sarifutils.GetRulesPropertyCount("applicability", "applicable", results...), "applicable cves") } diff --git a/jas/common.go b/jas/common.go index 8c060fd2c..d81aac402 100644 --- a/jas/common.go +++ b/jas/common.go @@ -17,6 +17,7 @@ import ( "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/jfrog/jfrog-cli-security/utils/techutils" goclientutils "github.com/jfrog/jfrog-client-go/utils" @@ -368,3 +369,23 @@ func CreateScannerTempDirectory(scanner *JasScanner, scanType string) (string, e } return scannerTempDir, nil } + +func FilterSkipNotApplicable(results *results.SecurityCommandResults) { + for _, target := range results.Targets { + filterScaResultsFromJas(target.ScaResults, target.JasResults) + } +} + +func filterScaResultsFromJas(scaResults *results.ScaScanResults, jasResults *results.JasScansResults) { + filteredViolations := []services.Violation{} + for i := range scaResults.XrayResults { + xrayResult := &scaResults.XrayResults[i] + for _, violation := range xrayResult.Violations { + if !sarifutils.IsRuleNameHasProperty("applicability", violation.Cves[0].Id, "not_applicable", jasResults.ApplicabilityScanResults...) { + filteredViolations = append(filteredViolations, violation) + xrayResult.Violations = append(xrayResult.Violations, violation) + } + } + xrayResult.Violations = filteredViolations + } +} diff --git a/utils/formats/sarifutils/sarifutils.go b/utils/formats/sarifutils/sarifutils.go index c0e8f3133..dbcba4aa3 100644 --- a/utils/formats/sarifutils/sarifutils.go +++ b/utils/formats/sarifutils/sarifutils.go @@ -2,10 +2,11 @@ package sarifutils import ( "fmt" - "github.com/jfrog/jfrog-cli-security/utils/jasutils" "path/filepath" "strings" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/owenrumney/go-sarif/v2/sarif" ) @@ -716,3 +717,14 @@ func GetResultFingerprint(result *sarif.Result) string { } return "" } + +func IsRuleNameHasProperty(property, name string, value string, runs ...*sarif.Run) bool { + for _, run := range runs { + for _, rule := range run.Tool.Driver.Rules { + if rule.Name != nil && *rule.Name == name { + return rule.Properties[property] != nil && rule.Properties[property] == value + } + } + } + return false +} diff --git a/utils/formats/sarifutils/sarifutils_test.go b/utils/formats/sarifutils/sarifutils_test.go index 11652d1e4..6ded3df57 100644 --- a/utils/formats/sarifutils/sarifutils_test.go +++ b/utils/formats/sarifutils/sarifutils_test.go @@ -1,10 +1,11 @@ package sarifutils import ( - "github.com/jfrog/jfrog-cli-security/utils/jasutils" "path/filepath" "testing" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" @@ -643,3 +644,50 @@ func TestGetResultFingerprint(t *testing.T) { assert.Equal(t, test.expectedOutput, GetResultFingerprint(test.result)) } } + +func TestIsRuleNameHasProperty(t *testing.T) { + tests := []struct { + name string + runs []*sarif.Run + parameters []string + expectedOutput bool + }{ + { + name: "Empty Run", + runs: []*sarif.Run{}, + parameters: []string{"applicability", "CVE-1111-123", "applicable"}, + expectedOutput: false, + }, + { + name: "Rule Has Applicable Property With CVE Name", + runs: []*sarif.Run{ + CreateRunsWithReportingDescriptor("applicability", "CVE-1111-123", "applicable"), + CreateRunsWithReportingDescriptor("applicability", "CVE-2222-123", "applicable"), + }, + parameters: []string{"applicability", "CVE-1111-123", "applicable"}, + expectedOutput: true, + }, + { + name: "UnMatching Names", + runs: []*sarif.Run{ + CreateRunsWithReportingDescriptor("applicability", "CVE-1111-123", "applicable"), + CreateRunsWithReportingDescriptor("applicability", "CVE-2222-123", "applicable"), + }, + parameters: []string{"applicability", "CVE-3333-123", "applicable"}, + expectedOutput: false, + }, + { + name: "UnMatching Property value", + runs: []*sarif.Run{ + CreateRunsWithReportingDescriptor("applicability", "CVE-1111-123", "not_applicable"), + CreateRunsWithReportingDescriptor("applicability", "CVE-2222-123", "applicable"), + }, + parameters: []string{"applicability", "CVE-1111-123", "applicable"}, + expectedOutput: false, + }, + } + + for _, test := range tests { + assert.Equal(t, IsRuleNameHasProperty(test.parameters[0], test.parameters[1], test.parameters[2], test.runs...), test.expectedOutput) + } +} diff --git a/utils/formats/sarifutils/test_sarifutils.go b/utils/formats/sarifutils/test_sarifutils.go index 78cac6924..a257cb49a 100644 --- a/utils/formats/sarifutils/test_sarifutils.go +++ b/utils/formats/sarifutils/test_sarifutils.go @@ -164,3 +164,17 @@ func CreateThreadFlow(locations ...*sarif.Location) *sarif.ThreadFlow { } return stackStrace } + +func CreateRunsWithReportingDescriptor(property, name string, value string) *sarif.Run { + run := CreateRunWithDummyResults( + CreateDummyPassingResult("rule"), + ) + for _, rule := range run.Tool.Driver.Rules { + rule.Name = &name + rule.Properties = map[string]interface{}{ + property: value, + } + } + + return run +} diff --git a/utils/xray/scangraph/params.go b/utils/xray/scangraph/params.go index eb51f2ee4..04f91a7b0 100644 --- a/utils/xray/scangraph/params.go +++ b/utils/xray/scangraph/params.go @@ -6,11 +6,12 @@ import ( ) type ScanGraphParams struct { - serverDetails *config.ServerDetails - xrayGraphScanParams *services.XrayGraphScanParams - fixableOnly bool - xrayVersion string - severityLevel int + serverDetails *config.ServerDetails + xrayGraphScanParams *services.XrayGraphScanParams + fixableOnly bool + skipNotApplicableCves bool + xrayVersion string + severityLevel int } type CommonGraphScanParams struct { @@ -66,3 +67,12 @@ func (sgp *ScanGraphParams) SetFixableOnly(fixable bool) *ScanGraphParams { sgp.fixableOnly = fixable return sgp } + +func (sgp *ScanGraphParams) SkipNotApplicableCves() bool { + return sgp.skipNotApplicableCves +} + +func (sgp *ScanGraphParams) SetSkipNotApplicableCves(skip bool) *ScanGraphParams { + sgp.skipNotApplicableCves = skip + return sgp +}