Skip to content

Commit

Permalink
malicious-code-scanner
Browse files Browse the repository at this point in the history
  • Loading branch information
barv-jfrog committed Feb 27, 2025
2 parents 2e773c7 + 6075488 commit 06b1175
Show file tree
Hide file tree
Showing 30 changed files with 954 additions and 70 deletions.
8 changes: 5 additions & 3 deletions cli/docs/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const (
Watches = "watches"
RepoPath = "repo-path"
Licenses = "licenses"
Sbom = "sbom"
Fail = "fail"
ExtendedTable = "extended-table"
MinSeverity = "min-severity"
Expand Down Expand Up @@ -144,7 +145,7 @@ var commandFlags = map[string][]string{
OfflineUpdate: {LicenseId, From, To, Version, Target, Stream, Periodic},
XrScan: {
url, user, password, accessToken, ServerId, SpecFlag, Threads, scanRecursive, scanRegexp, scanAnt,
Project, Watches, RepoPath, Licenses, OutputFormat, Fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, ScanVuln,
Project, Watches, RepoPath, Licenses, Sbom, OutputFormat, Fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, ScanVuln,
},
Enrich: {
url, user, password, accessToken, ServerId, Threads,
Expand All @@ -153,10 +154,10 @@ 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, Sbom, OutputFormat, Fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, ScanVuln, SecretValidation,
},
Audit: {
url, xrayUrl, user, password, accessToken, ServerId, InsecureTls, Project, Watches, RepoPath, Licenses, OutputFormat, ExcludeTestDeps,
url, xrayUrl, user, password, accessToken, ServerId, InsecureTls, Project, Watches, RepoPath, Sbom, 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, MaxTreeDepth,
Expand Down Expand Up @@ -226,6 +227,7 @@ var flagsMap = map[string]components.Flag{
Watches: components.NewStringFlag(Watches, "A comma-separated(,) list of Xray watches, to enable Xray to determine violations accordingly. The command accepts this option only if the --project and --repo-path options are not provided. If none of the three options are provided, the command will show all known vulnerabilities."),
RepoPath: components.NewStringFlag(RepoPath, "Artifactory repository path, to enable Xray to determine violations accordingly. The command accepts this option only if the --project and --watches options are not provided. If none of the three options are provided, the command will show all known vulnerabilities."),
Licenses: components.NewBoolFlag(Licenses, "Set if you'd also like the list of licenses to be displayed."),
Sbom: components.NewBoolFlag(Sbom, fmt.Sprintf("For displaying the SBOM for this project, set to true. Relevant only with --%s flag. Ignored if provided 'format' is not 'table'.", Sca)),
OutputFormat: components.NewStringFlag(
OutputFormat,
"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.",
Expand Down
12 changes: 12 additions & 0 deletions cli/scancommands.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ func ScanCmd(c *components.Context) error {
if err != nil {
return err
}
if c.GetBoolFlagValue(flags.Sbom) && format != outputFormat.Table {
log.Warn("The '--sbom' flag is only supported with the 'table' output format. Ignoring the flag.")
}
pluginsCommon.FixWinPathsForFileSystemSourcedCmds(specFile, c)
minSeverity, err := getMinimumSeverity(c)
if err != nil {
Expand All @@ -245,6 +248,7 @@ func ScanCmd(c *components.Context) error {
SetBaseRepoPath(repoPath).
SetIncludeVulnerabilities(c.GetBoolFlagValue(flags.Vuln) || shouldIncludeVulnerabilities(c)).
SetIncludeLicenses(c.GetBoolFlagValue(flags.Licenses)).
SetIncludeSbom(c.GetBoolFlagValue(flags.Sbom)).
SetFail(c.GetBoolFlagValue(flags.Fail)).
SetPrintExtendedTable(c.GetBoolFlagValue(flags.ExtendedTable)).
SetBypassArchiveLimits(c.GetBoolFlagValue(flags.BypassArchiveLimits)).
Expand Down Expand Up @@ -393,6 +397,9 @@ func CreateAuditCmd(c *components.Context) (string, string, *coreConfig.ServerDe
if err != nil {
return "", "", nil, nil, err
}
if c.GetBoolFlagValue(flags.Sbom) && format != outputFormat.Table {
log.Warn("The '--sbom' flag is only supported with the 'table' output format. Ignoring the flag.")
}
minSeverity, err := getMinimumSeverity(c)
if err != nil {
return "", "", nil, nil, err
Expand All @@ -406,6 +413,7 @@ func CreateAuditCmd(c *components.Context) (string, string, *coreConfig.ServerDe
SetProject(getProject(c)).
SetIncludeVulnerabilities(c.GetBoolFlagValue(flags.Vuln)).
SetIncludeLicenses(c.GetBoolFlagValue(flags.Licenses)).
SetIncludeSbom(c.GetBoolFlagValue(flags.Sbom)).
SetFail(c.GetBoolFlagValue(flags.Fail)).
SetPrintExtendedTable(c.GetBoolFlagValue(flags.ExtendedTable)).
SetMinSeverityFilter(minSeverity).
Expand Down Expand Up @@ -651,6 +659,9 @@ func DockerScan(c *components.Context, image string) error {
if err != nil {
return err
}
if c.GetBoolFlagValue(flags.Sbom) && format != outputFormat.Table {
log.Warn("The '--sbom' flag is only supported with the 'table' output format. Ignoring the flag.")
}
minSeverity, err := getMinimumSeverity(c)
if err != nil {
return err
Expand All @@ -664,6 +675,7 @@ func DockerScan(c *components.Context, image string) error {
SetBaseRepoPath(addTrailingSlashToRepoPathIfNeeded(c)).
SetIncludeVulnerabilities(c.GetBoolFlagValue(flags.Vuln) || shouldIncludeVulnerabilities(c)).
SetIncludeLicenses(c.GetBoolFlagValue(flags.Licenses)).
SetIncludeSbom(c.GetBoolFlagValue(flags.Sbom)).
SetFail(c.GetBoolFlagValue(flags.Fail)).
SetPrintExtendedTable(c.GetBoolFlagValue(flags.ExtendedTable)).
SetBypassArchiveLimits(c.GetBoolFlagValue(flags.BypassArchiveLimits)).
Expand Down
34 changes: 21 additions & 13 deletions commands/audit/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type AuditCommand struct {
targetRepoPath string
IncludeVulnerabilities bool
IncludeLicenses bool
IncludeSbom bool
Fail bool
PrintExtendedTable bool
Threads int
Expand Down Expand Up @@ -81,6 +82,11 @@ func (auditCmd *AuditCommand) SetIncludeLicenses(include bool) *AuditCommand {
return auditCmd
}

func (auditCmd *AuditCommand) SetIncludeSbom(include bool) *AuditCommand {
auditCmd.IncludeSbom = include
return auditCmd
}

func (auditCmd *AuditCommand) SetFail(fail bool) *AuditCommand {
auditCmd.Fail = fail
return auditCmd
Expand All @@ -97,13 +103,14 @@ func (auditCmd *AuditCommand) SetThreads(threads int) *AuditCommand {
}

// Create a results context based on the provided parameters. resolves conflicts between the parameters based on the retrieved platform watches.
func CreateAuditResultsContext(serverDetails *config.ServerDetails, xrayVersion string, watches []string, artifactoryRepoPath, projectKey, gitRepoHttpsCloneUrl string, includeVulnerabilities, includeLicenses bool) (context results.ResultContext) {
func CreateAuditResultsContext(serverDetails *config.ServerDetails, xrayVersion string, watches []string, artifactoryRepoPath, projectKey, gitRepoHttpsCloneUrl string, includeVulnerabilities, includeLicenses, includeSbom bool) (context results.ResultContext) {
context = results.ResultContext{
RepoPath: artifactoryRepoPath,
Watches: watches,
ProjectKey: projectKey,
IncludeVulnerabilities: shouldIncludeVulnerabilities(includeVulnerabilities, watches, artifactoryRepoPath, projectKey, ""),
IncludeLicenses: includeLicenses,
IncludeSbom: includeSbom,
}
if err := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, services.MinXrayVersionGitRepoKey); err != nil {
// Git repo key is not supported by the Xray version.
Expand Down Expand Up @@ -174,6 +181,7 @@ func (auditCmd *AuditCommand) Run() (err error) {
auditCmd.gitRepoHttpsCloneUrl,
auditCmd.IncludeVulnerabilities,
auditCmd.IncludeLicenses,
auditCmd.IncludeSbom,
)).
SetThirdPartyApplicabilityScan(auditCmd.thirdPartyApplicabilityScan).
SetThreads(auditCmd.Threads).
Expand All @@ -193,12 +201,12 @@ func (auditCmd *AuditCommand) Run() (err error) {
return ProcessResultsAndOutput(auditResults, auditCmd.getResultWriter(auditResults), auditCmd.Fail)
}

func (auditCmd *AuditCommand) getResultWriter(auditResults *results.SecurityCommandResults) *output.ResultsWriter {
func (auditCmd *AuditCommand) getResultWriter(cmdResults *results.SecurityCommandResults) *output.ResultsWriter {
var messages []string
if !auditResults.EntitledForJas {
if !cmdResults.EntitledForJas {
messages = []string{coreutils.PrintTitle("The ‘jf audit’ command also supports JFrog Advanced Security features, such as 'Contextual Analysis', 'Secret Detection', 'Malicious Code Scan', 'IaC Scan' and ‘SAST’.\nThis feature isn't enabled on your system. Read more - ") + coreutils.PrintLink(utils.JasInfoURL)}
}
return output.NewResultsWriter(auditResults).
return output.NewResultsWriter(cmdResults).
SetOutputFormat(auditCmd.OutputFormat()).
SetPrintExtendedTable(auditCmd.PrintExtendedTable).
SetExtraMessages(messages).
Expand Down Expand Up @@ -228,37 +236,37 @@ func (auditCmd *AuditCommand) CommandName() string {
// Runs an audit scan based on the provided auditParams.
// Returns an audit Results object containing all the scan results.
// If the current server is entitled for JAS, the advanced security results will be included in the scan results.
func RunAudit(auditParams *AuditParams) (auditResults *results.SecurityCommandResults) {
func RunAudit(auditParams *AuditParams) (cmdResults *results.SecurityCommandResults) {
// Initialize Results struct
if auditResults = initAuditCmdResults(auditParams); auditResults.GeneralError != nil {
if cmdResults = initAuditCmdResults(auditParams); cmdResults.GeneralError != nil {
return
}
jfrogAppsConfig, err := jas.CreateJFrogAppsConfig(auditResults.GetTargetsPaths())
jfrogAppsConfig, err := jas.CreateJFrogAppsConfig(cmdResults.GetTargetsPaths())
if err != nil {
return auditResults.AddGeneralError(fmt.Errorf("failed to create JFrogAppsConfig: %s", err.Error()), false)
return cmdResults.AddGeneralError(fmt.Errorf("failed to create JFrogAppsConfig: %s", err.Error()), false)
}
// Initialize the parallel runner
auditParallelRunner := utils.CreateSecurityParallelRunner(auditParams.threads)
// Add the JAS scans to the parallel runner
var jasScanner *jas.JasScanner
var generalJasScanErr error
if jasScanner, generalJasScanErr = RunJasScans(auditParallelRunner, auditParams, auditResults, jfrogAppsConfig); generalJasScanErr != nil {
auditResults.AddGeneralError(fmt.Errorf("error has occurred during JAS scan process. JAS scan is skipped for the following directories: %s\n%s", strings.Join(auditResults.GetTargetsPaths(), ","), generalJasScanErr.Error()), auditParams.AllowPartialResults())
if jasScanner, generalJasScanErr = RunJasScans(auditParallelRunner, auditParams, cmdResults, jfrogAppsConfig); generalJasScanErr != nil {
cmdResults.AddGeneralError(fmt.Errorf("error has occurred during JAS scan process. JAS scan is skipped for the following directories: %s\n%s", strings.Join(cmdResults.GetTargetsPaths(), ","), generalJasScanErr.Error()), auditParams.AllowPartialResults())
}
if auditParams.Progress() != nil {
auditParams.Progress().SetHeadlineMsg("Scanning for issues")
}
// The sca scan doesn't require the analyzer manager, so it can run separately from the analyzer manager download routine.
if generalScaScanError := buildDepTreeAndRunScaScan(auditParallelRunner, auditParams, auditResults); generalScaScanError != nil {
auditResults.AddGeneralError(fmt.Errorf("error has occurred during SCA scan process. SCA scan is skipped for the following directories: %s\n%s", strings.Join(auditResults.GetTargetsPaths(), ","), generalScaScanError.Error()), auditParams.AllowPartialResults())
if generalScaScanError := buildDepTreeAndRunScaScan(auditParallelRunner, auditParams, cmdResults); generalScaScanError != nil {
cmdResults.AddGeneralError(fmt.Errorf("error has occurred during SCA scan process. SCA scan is skipped for the following directories: %s\n%s", strings.Join(cmdResults.GetTargetsPaths(), ","), generalScaScanError.Error()), auditParams.AllowPartialResults())
}
go func() {
auditParallelRunner.ScaScansWg.Wait()
auditParallelRunner.JasWg.Wait()
// Wait for all jas scanners to complete before cleaning up scanners temp dir
auditParallelRunner.JasScannersWg.Wait()
if jasScanner != nil && jasScanner.ScannerDirCleanupFunc != nil {
auditResults.AddGeneralError(jasScanner.ScannerDirCleanupFunc(), false)
cmdResults.AddGeneralError(jasScanner.ScannerDirCleanupFunc(), false)
}
auditParallelRunner.Runner.Done()
}()
Expand Down
9 changes: 8 additions & 1 deletion commands/audit/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,20 +666,24 @@ func TestCreateResultsContext(t *testing.T) {
jfrogProjectKey string
includeVulnerabilities bool
includeLicenses bool
includeSbom bool

expectedArtifactoryRepoPath string
expectedHttpCloneUrl string
expectedWatches []string
expectedJfrogProjectKey string
expectedIncludeVulnerabilities bool
expectedIncludeLicenses bool
expectedIncludeSbom bool
}{
{
name: "Only Vulnerabilities",
includeLicenses: true,
includeSbom: true,
// Since no violation context is provided, the includeVulnerabilities flag should be set to true even if not provided
expectedIncludeVulnerabilities: true,
expectedIncludeLicenses: true,
expectedIncludeSbom: true,
},
{
name: "Watches",
Expand Down Expand Up @@ -711,25 +715,28 @@ func TestCreateResultsContext(t *testing.T) {
jfrogProjectKey: mockProjectKey,
includeVulnerabilities: true,
includeLicenses: true,
includeSbom: true,

expectedHttpCloneUrl: testCaseExpectedGitRepoHttpsCloneUrl,
expectedWatches: mockWatches,
expectedJfrogProjectKey: mockProjectKey,
expectedIncludeVulnerabilities: true,
expectedIncludeLicenses: true,
expectedIncludeSbom: true,
},
}
for _, testCase := range testCases {
t.Run(fmt.Sprintf("%s - %s", test.name, testCase.name), func(t *testing.T) {
mockServer, serverDetails := validations.XrayServer(t, validations.MockServerParams{XrayVersion: test.xrayVersion, ReturnMockPlatformWatches: test.expectedPlatformWatches})
defer mockServer.Close()
context := CreateAuditResultsContext(serverDetails, test.xrayVersion, testCase.watches, testCase.artifactoryRepoPath, testCase.jfrogProjectKey, testCase.httpCloneUrl, testCase.includeVulnerabilities, testCase.includeLicenses)
context := CreateAuditResultsContext(serverDetails, test.xrayVersion, testCase.watches, testCase.artifactoryRepoPath, testCase.jfrogProjectKey, testCase.httpCloneUrl, testCase.includeVulnerabilities, testCase.includeLicenses, testCase.includeSbom)
assert.Equal(t, testCase.expectedArtifactoryRepoPath, context.RepoPath)
assert.Equal(t, testCase.expectedHttpCloneUrl, context.GitRepoHttpsCloneUrl)
assert.Equal(t, testCase.expectedWatches, context.Watches)
assert.Equal(t, testCase.expectedJfrogProjectKey, context.ProjectKey)
assert.Equal(t, testCase.expectedIncludeVulnerabilities, context.IncludeVulnerabilities)
assert.Equal(t, testCase.expectedIncludeLicenses, context.IncludeLicenses)
assert.Equal(t, testCase.expectedIncludeSbom, context.IncludeSbom)
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions commands/audit/sca/pnpm/pnpm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestBuildDependencyTreeLimitedDepth(t *testing.T) {
name: "With transitive dependencies",
treeDepth: "1",
expectedUniqueDeps: []string{
"npm://axios:1.7.9",
"npm://axios:1.8.1",
"npm://balaganjs:1.0.0",
"npm://yargs:13.3.0",
"npm://zen-website:1.0.0",
Expand All @@ -54,7 +54,7 @@ func TestBuildDependencyTreeLimitedDepth(t *testing.T) {
Nodes: []*xrayUtils.GraphNode{
{
Id: "npm://balaganjs:1.0.0",
Nodes: []*xrayUtils.GraphNode{{Id: "npm://axios:1.7.9"}, {Id: "npm://yargs:13.3.0"}},
Nodes: []*xrayUtils.GraphNode{{Id: "npm://axios:1.8.1"}, {Id: "npm://yargs:13.3.0"}},
},
},
},
Expand Down
7 changes: 4 additions & 3 deletions commands/audit/scarunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"

"github.com/jfrog/jfrog-cli-security/commands/audit/sca/swift"

biutils "github.com/jfrog/build-info-go/utils"
Expand Down Expand Up @@ -107,10 +108,10 @@ func buildDepTreeAndRunScaScan(auditParallelRunner *utils.SecurityParallelRunner
auditParallelRunner.ScaScansWg.Add(1)
// defer auditParallelRunner.ScaScansWg.Done()
_, taskErr := auditParallelRunner.Runner.AddTaskWithError(executeScaScanTask(auditParallelRunner, serverDetails, auditParams, targetResult, treeResult), func(err error) {
_ = targetResult.AddTargetError(fmt.Errorf("Failed to execute SCA scan: %s", err.Error()), auditParams.AllowPartialResults())
_ = targetResult.AddTargetError(fmt.Errorf("failed to execute SCA scan: %s", err.Error()), auditParams.AllowPartialResults())
})
if taskErr != nil {
_ = targetResult.AddTargetError(fmt.Errorf("Failed to create SCA scan task: %s", taskErr.Error()), auditParams.AllowPartialResults())
_ = targetResult.AddTargetError(fmt.Errorf("failed to create SCA scan task: %s", taskErr.Error()), auditParams.AllowPartialResults())
auditParallelRunner.ScaScansWg.Done()
}
}
Expand All @@ -137,7 +138,7 @@ func executeScaScanTask(auditParallelRunner *utils.SecurityParallelRunner, serve
auditParallelRunner.ResultsMu.Lock()
defer auditParallelRunner.ResultsMu.Unlock()
// We add the results before checking for errors, so we can display the results even if an error occurred.
scan.NewScaScanResults(sca.GetScaScansStatusCode(xrayErr, scanResults...), scanResults...).IsMultipleRootProject = clientutils.Pointer(len(treeResult.FullDepTrees) > 1)
scan.NewScaScanResults(sca.GetScaScansStatusCode(xrayErr, scanResults...), results.DepTreeToSbom(treeResult.FullDepTrees), scanResults...).IsMultipleRootProject = clientutils.Pointer(len(treeResult.FullDepTrees) > 1)
addThirdPartyDependenciesToParams(auditParams, scan.Technology, treeResult.FlatTree, treeResult.FullDepTrees)

if xrayErr != nil {
Expand Down
Loading

0 comments on commit 06b1175

Please sign in to comment.