Skip to content

Commit 34c51b0

Browse files
committed
Malicious code scanner
1 parent d170e3e commit 34c51b0

File tree

27 files changed

+1178
-15
lines changed

27 files changed

+1178
-15
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, XrayUrl, user, password, accessToken, ServerId, Threads, InsecureTls,
177178
},
179+
MaliciousScan: {
180+
Url, XrayUrl, user, password, accessToken, ServerId, Threads, InsecureTls, OutputFormat, MinSeverity, AnalyzerManagerCustomPath, WorkingDirs,
181+
},
178182
BuildScan: {
179183
Url, XrayUrl, 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 "[Beta] Scan malicious models (pickle files, etc.) located in the working directory."
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: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
package maliciousscan
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/jfrog/jfrog-cli-core/v2/common/format"
10+
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
11+
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
12+
"github.com/jfrog/jfrog-cli-security/jas"
13+
"github.com/jfrog/jfrog-cli-security/jas/maliciouscode"
14+
"github.com/jfrog/jfrog-cli-security/utils"
15+
"github.com/jfrog/jfrog-cli-security/utils/jasutils"
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+
xrayVersion, entitledForJas, workingDirs, err := cmd.validateAndPrepare()
83+
if err != nil {
84+
return err
85+
}
86+
87+
cmdResults := cmd.initializeCommandResults(xrayVersion, entitledForJas)
88+
populateScanTargets(cmdResults, workingDirs)
89+
90+
scanner, err := cmd.createJasScanner()
91+
if err != nil {
92+
return err
93+
}
94+
95+
if err = cmd.runMaliciousScans(cmdResults, scanner); err != nil {
96+
return err
97+
}
98+
99+
return cmd.outputResults(cmdResults)
100+
}
101+
102+
func (cmd *MaliciousScanCommand) validateAndPrepare() (xrayVersion string, entitledForJas bool, workingDirs []string, err error) {
103+
xrayManager, xrayVersion, err := xray.CreateXrayServiceManagerAndGetVersion(cmd.serverDetails)
104+
if err != nil {
105+
return "", false, nil, err
106+
}
107+
108+
entitledForJas, err = jas.IsEntitledForJas(xrayManager, xrayVersion)
109+
if err != nil {
110+
return "", false, nil, err
111+
}
112+
if !entitledForJas {
113+
return "", false, nil, errors.New("JAS (Advanced Security) feature is not entitled")
114+
}
115+
116+
log.Info("JFrog Xray version is:", xrayVersion)
117+
118+
workingDirs, err = coreutils.GetFullPathsWorkingDirs(cmd.workingDirs)
119+
if err != nil {
120+
return "", false, nil, err
121+
}
122+
logScanPaths(workingDirs)
123+
124+
return xrayVersion, entitledForJas, workingDirs, nil
125+
}
126+
127+
func (cmd *MaliciousScanCommand) initializeCommandResults(xrayVersion string, entitledForJas bool) *results.SecurityCommandResults {
128+
cmdResults := results.NewCommandResults(utils.SourceCode)
129+
cmdResults.SetXrayVersion(xrayVersion)
130+
cmdResults.SetEntitledForJas(entitledForJas)
131+
cmdResults.SetResultsContext(results.ResultContext{
132+
IncludeVulnerabilities: true,
133+
})
134+
return cmdResults
135+
}
136+
137+
func (cmd *MaliciousScanCommand) createJasScanner() (*jas.JasScanner, error) {
138+
scannerOptions := []jas.JasScannerOption{
139+
jas.WithEnvVars(
140+
false,
141+
jas.NotDiffScanEnvValue,
142+
jas.GetAnalyzerManagerXscEnvVars(
143+
"",
144+
"",
145+
"",
146+
nil,
147+
),
148+
),
149+
jas.WithMinSeverity(cmd.minSeverityFilter),
150+
}
151+
152+
scanner, err := jas.NewJasScanner(cmd.serverDetails, scannerOptions...)
153+
if err != nil {
154+
return nil, fmt.Errorf("failed to create JAS scanner: %w", err)
155+
}
156+
if scanner == nil {
157+
return nil, errors.New("JAS scanner was not created")
158+
}
159+
160+
if err = cmd.setAnalyzerManagerPath(scanner); err != nil {
161+
return nil, err
162+
}
163+
164+
log.Debug(fmt.Sprintf("Using analyzer manager executable at: %s", scanner.AnalyzerManager.AnalyzerManagerFullPath))
165+
return scanner, nil
166+
}
167+
168+
func (cmd *MaliciousScanCommand) setAnalyzerManagerPath(scanner *jas.JasScanner) error {
169+
if cmd.customAnalyzerManagerPath == "" {
170+
if err := jas.DownloadAnalyzerManagerIfNeeded(0); err != nil {
171+
return fmt.Errorf("failed to download analyzer manager: %s", err.Error())
172+
}
173+
var err error
174+
if scanner.AnalyzerManager.AnalyzerManagerFullPath, err = jas.GetAnalyzerManagerExecutable(); err != nil {
175+
return fmt.Errorf("failed to set analyzer manager executable path: %s", err.Error())
176+
}
177+
} else {
178+
scanner.AnalyzerManager.AnalyzerManagerFullPath = cmd.customAnalyzerManagerPath
179+
log.Debug("using custom analyzer manager binary path")
180+
}
181+
return nil
182+
}
183+
184+
func (cmd *MaliciousScanCommand) runMaliciousScans(cmdResults *results.SecurityCommandResults, scanner *jas.JasScanner) error {
185+
jasScanProducerConsumer := utils.NewSecurityParallelRunner(cmd.threads)
186+
jasScanProducerConsumer.JasWg.Add(1)
187+
createMaliciousScansTask := func(threadId int) (generalError error) {
188+
defer func() {
189+
jasScanProducerConsumer.JasWg.Done()
190+
}()
191+
for _, targetResult := range cmdResults.Targets {
192+
vulnerabilitiesResults, err := maliciouscode.RunMaliciousScan(
193+
scanner,
194+
maliciouscode.MaliciousScannerType,
195+
targetResult.Target,
196+
len(cmdResults.Targets),
197+
threadId,
198+
)
199+
jasScanProducerConsumer.ResultsMu.Lock()
200+
// Malicious code scans only return vulnerabilities, not violations
201+
targetResult.AddJasScanResults(jasutils.MaliciousCode, vulnerabilitiesResults, nil, jas.GetAnalyzerManagerExitCode(err))
202+
jasScanProducerConsumer.ResultsMu.Unlock()
203+
if err = jas.ParseAnalyzerManagerError(jasutils.MaliciousCode, err); err != nil {
204+
_ = targetResult.AddTargetError(fmt.Errorf("failed to run malicious scan: %w", err), false)
205+
}
206+
}
207+
return
208+
}
209+
210+
if _, addTaskErr := jasScanProducerConsumer.Runner.AddTaskWithError(createMaliciousScansTask, func(taskErr error) {
211+
cmdResults.AddGeneralError(fmt.Errorf("failed while adding malicious scan tasks: %s", taskErr.Error()), false)
212+
}); addTaskErr != nil {
213+
return fmt.Errorf("failed to create malicious scan task: %w", addTaskErr)
214+
}
215+
216+
jasScanProducerConsumer.Start()
217+
return nil
218+
}
219+
220+
func (cmd *MaliciousScanCommand) outputResults(cmdResults *results.SecurityCommandResults) error {
221+
if err := output.NewResultsWriter(cmdResults).
222+
SetOutputFormat(cmd.outputFormat).
223+
SetPlatformUrl(cmd.serverDetails.Url).
224+
SetPrintExtendedTable(false).
225+
SetIsMultipleRootProject(cmdResults.HasMultipleTargets()).
226+
SetSubScansPerformed([]utils.SubScanType{utils.MaliciousCodeScan}).
227+
PrintScanResults(); err != nil {
228+
return errors.Join(err, cmdResults.GetErrors())
229+
}
230+
231+
if err := cmdResults.GetErrors(); err != nil {
232+
return err
233+
}
234+
235+
log.Info("Malicious scan completed successfully.")
236+
return nil
237+
}
238+
239+
func logScanPaths(workingDirs []string) {
240+
if len(workingDirs) == 0 {
241+
return
242+
}
243+
if len(workingDirs) == 1 {
244+
log.Debug("Scanning path:", workingDirs[0])
245+
return
246+
}
247+
log.Debug("Scanning paths:", strings.Join(workingDirs, ", "))
248+
}
249+
250+
func populateScanTargets(cmdResults *results.SecurityCommandResults, workingDirs []string) {
251+
for _, requestedDirectory := range workingDirs {
252+
if !fileutils.IsPathExists(requestedDirectory, false) {
253+
log.Warn("The working directory", requestedDirectory, "doesn't exist. Skipping scan...")
254+
continue
255+
}
256+
cmdResults.NewScanResults(results.ScanTarget{Target: requestedDirectory, Name: filepath.Base(requestedDirectory)})
257+
}
258+
259+
if len(cmdResults.Targets) == 0 {
260+
log.Warn("No scan targets were detected.")
261+
return
262+
}
263+
264+
logScanTargetsInfo(cmdResults)
265+
}
266+
267+
func logScanTargetsInfo(cmdResults *results.SecurityCommandResults) {
268+
if len(cmdResults.Targets) == 0 {
269+
return
270+
}
271+
log.Info("Scanning", len(cmdResults.Targets), "target(s)...")
272+
for _, targetResult := range cmdResults.Targets {
273+
log.Info("Scanning target:", targetResult.Target)
274+
}
275+
}

jas/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ type SpecificScannersExcludePatterns struct {
6060
SastExcludePatterns []string
6161
SecretsExcludePatterns []string
6262
IacExcludePatterns []string
63+
MaliciousCodeExcludePatterns []string
6364
}
6465

6566
type JasScannerOption func(f *JasScanner) error

0 commit comments

Comments
 (0)