Skip to content

Commit ff68bae

Browse files
committed
Malicious code scanner
Malicious code scanner
1 parent 30c2fe0 commit ff68bae

File tree

29 files changed

+1239
-23
lines changed

29 files changed

+1239
-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"
@@ -175,6 +176,9 @@ var commandFlags = map[string][]string{
175176
Enrich: {
176177
Url, user, password, accessToken, ServerId, Threads, InsecureTls,
177178
},
179+
MaliciousScan: {
180+
Url, user, password, accessToken, ServerId, Threads, InsecureTls, OutputFormat, MinSeverity, AnalyzerManagerCustomPath, WorkingDirs,
181+
},
178182
BuildScan: {
179183
Url, user, password, accessToken, ServerId, scanProjectKey, BuildVuln, OutputFormat, Fail, ExtendedTable, Rescan, InsecureTls, TriggerScanRetries,
180184
},

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: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
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+
workingDirs, err := coreutils.GetFullPathsWorkingDirs(cmd.workingDirs)
113+
if err != nil {
114+
return err
115+
}
116+
logScanPaths(workingDirs)
117+
118+
cmdResults := results.NewCommandResults(utils.SourceCode)
119+
cmdResults.SetXrayVersion(xrayVersion)
120+
cmdResults.SetEntitledForJas(entitledForJas)
121+
cmdResults.SetResultsContext(results.ResultContext{
122+
IncludeVulnerabilities: true,
123+
})
124+
125+
populateScanTargets(cmdResults, workingDirs)
126+
127+
scannerOptions := []jas.JasScannerOption{
128+
jas.WithEnvVars(
129+
false, // validateSecrets not relevant for malicious scan
130+
jas.NotDiffScanEnvValue,
131+
jas.GetAnalyzerManagerXscEnvVars(
132+
"", // msi
133+
"", // gitRepoUrl
134+
"", // projectKey
135+
nil, // watches
136+
),
137+
),
138+
jas.WithMinSeverity(cmd.minSeverityFilter),
139+
}
140+
141+
scanner, err := jas.NewJasScanner(cmd.serverDetails, scannerOptions...)
142+
if err != nil {
143+
return fmt.Errorf("failed to create JAS scanner: %w", err)
144+
}
145+
if scanner == nil {
146+
return errors.New("JAS scanner was not created")
147+
}
148+
149+
if cmd.customAnalyzerManagerPath != "" {
150+
scanner.AnalyzerManager.AnalyzerManagerFullPath = cmd.customAnalyzerManagerPath
151+
} else {
152+
if scanner.AnalyzerManager.AnalyzerManagerFullPath, err = jas.GetAnalyzerManagerExecutable(); err != nil {
153+
return fmt.Errorf("failed to set analyzer manager executable path: %w", err)
154+
}
155+
}
156+
157+
log.Debug(fmt.Sprintf("Using analyzer manager executable at: %s", scanner.AnalyzerManager.AnalyzerManagerFullPath))
158+
159+
jasScanProducerConsumer := utils.NewSecurityParallelRunner(cmd.threads)
160+
161+
serverDetails, err := cmd.ServerDetails()
162+
if err != nil {
163+
return err
164+
}
165+
166+
jasScanProducerConsumer.JasWg.Add(1)
167+
createMaliciousScansTask := func(threadId int) (generalError error) {
168+
defer func() {
169+
jasScanProducerConsumer.JasWg.Done()
170+
}()
171+
for _, targetResult := range cmdResults.Targets {
172+
if targetResult.AppsConfigModule == nil {
173+
_ = targetResult.AddTargetError(fmt.Errorf("can't find module for path %s", targetResult.Target), false)
174+
continue
175+
}
176+
appsConfigModule := *targetResult.AppsConfigModule
177+
jasParams := runner.JasRunnerParams{
178+
Runner: &jasScanProducerConsumer,
179+
ServerDetails: serverDetails,
180+
Scanner: scanner,
181+
Module: appsConfigModule,
182+
ScansToPerform: []utils.SubScanType{utils.MaliciousCodeScan},
183+
ScanResults: targetResult,
184+
TargetCount: len(cmdResults.Targets),
185+
}
186+
187+
if generalError = runner.AddJasScannersTasks(jasParams); generalError != nil {
188+
_ = targetResult.AddTargetError(fmt.Errorf("failed to add malicious scan task: %w", generalError), false)
189+
generalError = nil
190+
}
191+
}
192+
return
193+
}
194+
195+
if _, addTaskErr := jasScanProducerConsumer.Runner.AddTaskWithError(createMaliciousScansTask, func(taskErr error) {
196+
cmdResults.AddGeneralError(fmt.Errorf("failed while adding JAS scan tasks: %s", taskErr.Error()), false)
197+
}); addTaskErr != nil {
198+
return fmt.Errorf("failed to create JAS task: %w", addTaskErr)
199+
}
200+
201+
jasScanProducerConsumer.Start()
202+
203+
if cmd.progress != nil {
204+
if err = cmd.progress.Quit(); err != nil {
205+
return err
206+
}
207+
}
208+
209+
if err = output.NewResultsWriter(cmdResults).
210+
SetOutputFormat(cmd.outputFormat).
211+
SetPlatformUrl(cmd.serverDetails.Url).
212+
SetPrintExtendedTable(false).
213+
SetIsMultipleRootProject(cmdResults.HasMultipleTargets()).
214+
SetSubScansPerformed([]utils.SubScanType{utils.MaliciousCodeScan}).
215+
PrintScanResults(); err != nil {
216+
return errors.Join(err, cmdResults.GetErrors())
217+
}
218+
219+
if err = cmdResults.GetErrors(); err != nil {
220+
return err
221+
}
222+
223+
log.Info("Malicious scan completed successfully.")
224+
return nil
225+
}
226+
227+
func logScanPaths(workingDirs []string) {
228+
if len(workingDirs) == 0 {
229+
return
230+
}
231+
if len(workingDirs) == 1 {
232+
log.Info("Scanning path:", workingDirs[0])
233+
return
234+
}
235+
log.Info("Scanning paths:", strings.Join(workingDirs, ", "))
236+
}
237+
238+
func populateScanTargets(cmdResults *results.SecurityCommandResults, workingDirs []string) {
239+
for _, requestedDirectory := range workingDirs {
240+
if !fileutils.IsPathExists(requestedDirectory, false) {
241+
log.Warn("The working directory", requestedDirectory, "doesn't exist. Skipping scan...")
242+
continue
243+
}
244+
cmdResults.NewScanResults(results.ScanTarget{Target: requestedDirectory, Name: filepath.Base(requestedDirectory)})
245+
}
246+
247+
if len(workingDirs) == 0 {
248+
currentDir, err := coreutils.GetWorkingDirectory()
249+
if err != nil {
250+
cmdResults.AddGeneralError(fmt.Errorf("failed to get current working directory: %w", err), false)
251+
return
252+
}
253+
cmdResults.NewScanResults(results.ScanTarget{Target: currentDir, Name: filepath.Base(currentDir)})
254+
}
255+
256+
if len(cmdResults.Targets) == 0 {
257+
log.Warn("No scan targets were detected. Proceeding with empty scan...")
258+
return
259+
}
260+
261+
jfrogAppsConfig, err := jas.CreateJFrogAppsConfig(cmdResults.GetTargetsPaths())
262+
if err != nil {
263+
cmdResults.AddGeneralError(fmt.Errorf("failed to create JFrogAppsConfig: %w", err), false)
264+
return
265+
}
266+
267+
for _, targetResult := range cmdResults.Targets {
268+
targetResult.AppsConfigModule = jas.GetModule(targetResult.Target, jfrogAppsConfig)
269+
}
270+
271+
logScanTargetsInfo(cmdResults)
272+
}
273+
274+
func logScanTargetsInfo(cmdResults *results.SecurityCommandResults) {
275+
if len(cmdResults.Targets) == 0 {
276+
return
277+
}
278+
log.Info("Scanning", len(cmdResults.Targets), "target(s)...")
279+
for _, targetResult := range cmdResults.Targets {
280+
log.Info("Scanning target:", targetResult.Target)
281+
}
282+
}

0 commit comments

Comments
 (0)