Skip to content

Commit 9da5f8c

Browse files
committed
Malicious code scanner
1 parent 93636fe commit 9da5f8c

File tree

29 files changed

+1243
-23
lines changed

29 files changed

+1243
-23
lines changed

cli/docs/flags.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
GitCountContributors = "count-contributors"
2626
Enrich = "sbom-enrich"
2727
UploadCdx = "upload-cdx"
28+
MaliciousScan = "malicious-scan"
2829

2930
// TODO: Deprecated commands (remove at next CLI major version)
3031
AuditMvn = "audit-maven"
@@ -165,6 +166,9 @@ var commandFlags = map[string][]string{
165166
Enrich: {
166167
Url, user, password, accessToken, ServerId, Threads, InsecureTls,
167168
},
169+
MaliciousScan: {
170+
Url, user, password, accessToken, ServerId, Threads, InsecureTls, OutputFormat, MinSeverity, AnalyzerManagerCustomPath, WorkingDirs,
171+
},
168172
BuildScan: {
169173
Url, user, password, accessToken, ServerId, scanProjectKey, BuildVuln, OutputFormat, Fail, ExtendedTable, Rescan, InsecureTls, TriggerScanRetries,
170174
},

cli/docs/maliciousscan/help.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package maliciousscan
2+
3+
import (
4+
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
5+
)
6+
7+
func GetDescription() string {
8+
return "Scan malicious models (pickle files, etc.) located in the working directory with AnalyzerManager."
9+
}
10+
11+
func GetArguments() []components.Argument {
12+
return []components.Argument{}
13+
}

cli/scancommands.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
flags "github.com/jfrog/jfrog-cli-security/cli/docs"
2222
auditSpecificDocs "github.com/jfrog/jfrog-cli-security/cli/docs/auditspecific"
2323
enrichDocs "github.com/jfrog/jfrog-cli-security/cli/docs/enrich"
24+
maliciousScanDocs "github.com/jfrog/jfrog-cli-security/cli/docs/maliciousscan"
2425
mcpDocs "github.com/jfrog/jfrog-cli-security/cli/docs/mcp"
2526
auditDocs "github.com/jfrog/jfrog-cli-security/cli/docs/scan/audit"
2627
buildScanDocs "github.com/jfrog/jfrog-cli-security/cli/docs/scan/buildscan"
@@ -40,6 +41,7 @@ import (
4041

4142
"github.com/jfrog/jfrog-cli-security/commands/audit"
4243
"github.com/jfrog/jfrog-cli-security/commands/curation"
44+
"github.com/jfrog/jfrog-cli-security/commands/maliciousscan"
4345
"github.com/jfrog/jfrog-cli-security/commands/scan"
4446
"github.com/jfrog/jfrog-cli-security/commands/upload"
4547

@@ -72,6 +74,15 @@ func getAuditAndScansCommands() []components.Command {
7274
Category: securityCategory,
7375
Action: EnrichCmd,
7476
},
77+
{
78+
Name: "malicious-scan",
79+
Aliases: []string{"ms"},
80+
Flags: flags.GetCommandFlags(flags.MaliciousScan),
81+
Description: maliciousScanDocs.GetDescription(),
82+
Arguments: maliciousScanDocs.GetArguments(),
83+
Category: securityCategory,
84+
Action: MaliciousScanCmd,
85+
},
7586
{
7687
Name: "build-scan",
7788
Aliases: []string{"bs"},
@@ -230,6 +241,42 @@ func EnrichCmd(c *components.Context) error {
230241
return commandsCommon.Exec(EnrichCmd)
231242
}
232243

244+
func MaliciousScanCmd(c *components.Context) error {
245+
serverDetails, err := CreateServerDetailsFromFlags(c)
246+
if err != nil {
247+
return err
248+
}
249+
if err = validateConnectionInputs(serverDetails); err != nil {
250+
return err
251+
}
252+
format, err := outputFormat.GetOutputFormat(c.GetStringFlagValue(flags.OutputFormat))
253+
if err != nil {
254+
return err
255+
}
256+
threads, err := pluginsCommon.GetThreadsCount(c)
257+
if err != nil {
258+
return err
259+
}
260+
minSeverity, err := getMinimumSeverity(c)
261+
if err != nil {
262+
return err
263+
}
264+
workingDirs := []string{}
265+
if c.GetStringFlagValue(flags.WorkingDirs) != "" {
266+
workingDirs = splitByCommaAndTrim(c.GetStringFlagValue(flags.WorkingDirs))
267+
}
268+
maliciousScanCmd := maliciousscan.NewMaliciousScanCommand().
269+
SetServerDetails(serverDetails).
270+
SetWorkingDirs(workingDirs).
271+
SetThreads(threads).
272+
SetOutputFormat(format).
273+
SetMinSeverityFilter(minSeverity)
274+
if c.IsFlagSet(flags.AnalyzerManagerCustomPath) {
275+
maliciousScanCmd.SetCustomAnalyzerManagerPath(c.GetStringFlagValue(flags.AnalyzerManagerCustomPath))
276+
}
277+
return commandsCommon.Exec(maliciousScanCmd)
278+
}
279+
233280
func ScanCmd(c *components.Context) error {
234281
if len(c.Arguments) == 0 && !c.IsFlagSet(flags.SpecFlag) {
235282
return pluginsCommon.PrintHelpAndReturnError("providing either a <source pattern> argument or the 'spec' option is mandatory", c)
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
package maliciousscan
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/jfrog/jfrog-cli-core/v2/common/format"
11+
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
12+
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
13+
"github.com/jfrog/jfrog-cli-security/jas"
14+
"github.com/jfrog/jfrog-cli-security/jas/runner"
15+
"github.com/jfrog/jfrog-cli-security/utils"
16+
"github.com/jfrog/jfrog-cli-security/utils/results"
17+
"github.com/jfrog/jfrog-cli-security/utils/results/output"
18+
"github.com/jfrog/jfrog-cli-security/utils/severityutils"
19+
"github.com/jfrog/jfrog-cli-security/utils/xray"
20+
ioUtils "github.com/jfrog/jfrog-client-go/utils/io"
21+
"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
22+
"github.com/jfrog/jfrog-client-go/utils/log"
23+
)
24+
25+
type MaliciousScanCommand struct {
26+
serverDetails *config.ServerDetails
27+
workingDirs []string
28+
threads int
29+
outputFormat format.OutputFormat
30+
minSeverityFilter severityutils.Severity
31+
progress ioUtils.ProgressMgr
32+
customAnalyzerManagerPath string
33+
}
34+
35+
func (cmd *MaliciousScanCommand) SetProgress(progress ioUtils.ProgressMgr) {
36+
cmd.progress = progress
37+
}
38+
39+
func (cmd *MaliciousScanCommand) SetThreads(threads int) *MaliciousScanCommand {
40+
cmd.threads = threads
41+
return cmd
42+
}
43+
44+
func (cmd *MaliciousScanCommand) SetServerDetails(server *config.ServerDetails) *MaliciousScanCommand {
45+
cmd.serverDetails = server
46+
return cmd
47+
}
48+
49+
func (cmd *MaliciousScanCommand) SetWorkingDirs(workingDirs []string) *MaliciousScanCommand {
50+
cmd.workingDirs = workingDirs
51+
return cmd
52+
}
53+
54+
func (cmd *MaliciousScanCommand) SetOutputFormat(format format.OutputFormat) *MaliciousScanCommand {
55+
cmd.outputFormat = format
56+
return cmd
57+
}
58+
59+
func (cmd *MaliciousScanCommand) SetMinSeverityFilter(minSeverity severityutils.Severity) *MaliciousScanCommand {
60+
cmd.minSeverityFilter = minSeverity
61+
return cmd
62+
}
63+
64+
func (cmd *MaliciousScanCommand) SetCustomAnalyzerManagerPath(path string) *MaliciousScanCommand {
65+
cmd.customAnalyzerManagerPath = path
66+
return cmd
67+
}
68+
69+
func (cmd *MaliciousScanCommand) ServerDetails() (*config.ServerDetails, error) {
70+
return cmd.serverDetails, nil
71+
}
72+
73+
func (cmd *MaliciousScanCommand) CommandName() string {
74+
return "malicious_scan"
75+
}
76+
77+
func NewMaliciousScanCommand() *MaliciousScanCommand {
78+
return &MaliciousScanCommand{}
79+
}
80+
81+
func (cmd *MaliciousScanCommand) Run() (err error) {
82+
defer func() {
83+
if err != nil {
84+
var e *exec.ExitError
85+
if errors.As(err, &e) {
86+
if e.ExitCode() != coreutils.ExitCodeVulnerableBuild.Code {
87+
err = errors.New("Malicious scan command failed. " + err.Error())
88+
}
89+
}
90+
}
91+
}()
92+
93+
xrayManager, xrayVersion, err := xray.CreateXrayServiceManagerAndGetVersion(cmd.serverDetails)
94+
if err != nil {
95+
return err
96+
}
97+
98+
entitledForJas, err := jas.IsEntitledForJas(xrayManager, xrayVersion)
99+
if err != nil {
100+
return err
101+
}
102+
if !entitledForJas {
103+
return errors.New("JAS (Advanced Security) feature is not entitled")
104+
}
105+
106+
log.Info("JFrog Xray version is:", xrayVersion)
107+
108+
if err = jas.DownloadAnalyzerManagerIfNeeded(0); err != nil {
109+
return fmt.Errorf("failed to download Analyzer Manager: %w", err)
110+
}
111+
112+
isRecursiveScan := len(cmd.workingDirs) == 0
113+
workingDirs, err := coreutils.GetFullPathsWorkingDirs(cmd.workingDirs)
114+
if err != nil {
115+
return err
116+
}
117+
logScanPaths(workingDirs, isRecursiveScan)
118+
119+
cmdResults := results.NewCommandResults(utils.SourceCode)
120+
cmdResults.SetXrayVersion(xrayVersion)
121+
cmdResults.SetEntitledForJas(entitledForJas)
122+
cmdResults.SetResultsContext(results.ResultContext{
123+
IncludeVulnerabilities: true,
124+
})
125+
126+
populateScanTargets(cmdResults, workingDirs, isRecursiveScan)
127+
128+
scannerOptions := []jas.JasScannerOption{
129+
jas.WithEnvVars(
130+
false, // validateSecrets not relevant for malicious scan
131+
jas.NotDiffScanEnvValue,
132+
jas.GetAnalyzerManagerXscEnvVars(
133+
"", // msi
134+
"", // gitRepoUrl
135+
"", // projectKey
136+
nil, // watches
137+
),
138+
),
139+
jas.WithMinSeverity(cmd.minSeverityFilter),
140+
}
141+
142+
scanner, err := jas.NewJasScanner(cmd.serverDetails, scannerOptions...)
143+
if err != nil {
144+
return fmt.Errorf("failed to create JAS scanner: %w", err)
145+
}
146+
if scanner == nil {
147+
return errors.New("JAS scanner was not created")
148+
}
149+
150+
if cmd.customAnalyzerManagerPath != "" {
151+
scanner.AnalyzerManager.AnalyzerManagerFullPath = cmd.customAnalyzerManagerPath
152+
} else {
153+
if scanner.AnalyzerManager.AnalyzerManagerFullPath, err = jas.GetAnalyzerManagerExecutable(); err != nil {
154+
return fmt.Errorf("failed to set analyzer manager executable path: %w", err)
155+
}
156+
}
157+
158+
log.Debug(fmt.Sprintf("Using analyzer manager executable at: %s", scanner.AnalyzerManager.AnalyzerManagerFullPath))
159+
160+
jasScanProducerConsumer := utils.NewSecurityParallelRunner(cmd.threads)
161+
162+
serverDetails, err := cmd.ServerDetails()
163+
if err != nil {
164+
return err
165+
}
166+
167+
jasScanProducerConsumer.JasWg.Add(1)
168+
createMaliciousScansTask := func(threadId int) (generalError error) {
169+
defer func() {
170+
jasScanProducerConsumer.JasWg.Done()
171+
}()
172+
for _, targetResult := range cmdResults.Targets {
173+
if targetResult.AppsConfigModule == nil {
174+
_ = targetResult.AddTargetError(fmt.Errorf("can't find module for path %s", targetResult.Target), false)
175+
continue
176+
}
177+
appsConfigModule := *targetResult.AppsConfigModule
178+
jasParams := runner.JasRunnerParams{
179+
Runner: &jasScanProducerConsumer,
180+
ServerDetails: serverDetails,
181+
Scanner: scanner,
182+
Module: appsConfigModule,
183+
ScansToPerform: []utils.SubScanType{utils.MaliciousCodeScan},
184+
ScanResults: targetResult,
185+
TargetCount: len(cmdResults.Targets),
186+
}
187+
188+
if generalError = runner.AddJasScannersTasks(jasParams); generalError != nil {
189+
_ = targetResult.AddTargetError(fmt.Errorf("failed to add malicious scan task: %w", generalError), false)
190+
generalError = nil
191+
}
192+
}
193+
return
194+
}
195+
196+
if _, addTaskErr := jasScanProducerConsumer.Runner.AddTaskWithError(createMaliciousScansTask, func(taskErr error) {
197+
cmdResults.AddGeneralError(fmt.Errorf("failed while adding JAS scan tasks: %s", taskErr.Error()), false)
198+
}); addTaskErr != nil {
199+
return fmt.Errorf("failed to create JAS task: %w", addTaskErr)
200+
}
201+
202+
jasScanProducerConsumer.Start()
203+
204+
if cmd.progress != nil {
205+
if err = cmd.progress.Quit(); err != nil {
206+
return err
207+
}
208+
}
209+
210+
if err = output.NewResultsWriter(cmdResults).
211+
SetOutputFormat(cmd.outputFormat).
212+
SetPlatformUrl(cmd.serverDetails.Url).
213+
SetPrintExtendedTable(false).
214+
SetIsMultipleRootProject(cmdResults.HasMultipleTargets()).
215+
SetSubScansPerformed([]utils.SubScanType{utils.MaliciousCodeScan}).
216+
PrintScanResults(); err != nil {
217+
return errors.Join(err, cmdResults.GetErrors())
218+
}
219+
220+
if err = cmdResults.GetErrors(); err != nil {
221+
return err
222+
}
223+
224+
log.Info("Malicious scan completed successfully.")
225+
return nil
226+
}
227+
228+
func logScanPaths(workingDirs []string, isRecursiveScan bool) {
229+
if len(workingDirs) == 0 {
230+
return
231+
}
232+
if len(workingDirs) == 1 {
233+
if isRecursiveScan {
234+
log.Info("Detecting recursively targets for scan in path:", workingDirs[0])
235+
} else {
236+
log.Info("Scanning path:", workingDirs[0])
237+
}
238+
return
239+
}
240+
log.Info("Scanning paths:", strings.Join(workingDirs, ", "))
241+
}
242+
243+
func populateScanTargets(cmdResults *results.SecurityCommandResults, workingDirs []string, isRecursiveScan bool) {
244+
for _, requestedDirectory := range workingDirs {
245+
if !fileutils.IsPathExists(requestedDirectory, false) {
246+
log.Warn("The working directory", requestedDirectory, "doesn't exist. Skipping scan...")
247+
continue
248+
}
249+
cmdResults.NewScanResults(results.ScanTarget{Target: requestedDirectory, Name: filepath.Base(requestedDirectory)})
250+
}
251+
252+
if len(workingDirs) == 0 {
253+
currentDir, err := coreutils.GetWorkingDirectory()
254+
if err != nil {
255+
cmdResults.AddGeneralError(fmt.Errorf("failed to get current working directory: %w", err), false)
256+
return
257+
}
258+
cmdResults.NewScanResults(results.ScanTarget{Target: currentDir, Name: filepath.Base(currentDir)})
259+
}
260+
261+
if len(cmdResults.Targets) == 0 {
262+
log.Warn("No scan targets were detected. Proceeding with empty scan...")
263+
return
264+
}
265+
266+
jfrogAppsConfig, err := jas.CreateJFrogAppsConfig(cmdResults.GetTargetsPaths())
267+
if err != nil {
268+
cmdResults.AddGeneralError(fmt.Errorf("failed to create JFrogAppsConfig: %w", err), false)
269+
return
270+
}
271+
272+
for _, targetResult := range cmdResults.Targets {
273+
targetResult.AppsConfigModule = jas.GetModule(targetResult.Target, jfrogAppsConfig)
274+
}
275+
276+
logScanTargetsInfo(cmdResults)
277+
}
278+
279+
func logScanTargetsInfo(cmdResults *results.SecurityCommandResults) {
280+
if len(cmdResults.Targets) == 0 {
281+
return
282+
}
283+
log.Info("Scanning", len(cmdResults.Targets), "target(s)...")
284+
for _, targetResult := range cmdResults.Targets {
285+
log.Info("Scanning target:", targetResult.Target)
286+
}
287+
}

0 commit comments

Comments
 (0)