Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/jfrog/gofrog v1.7.6
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251211075913-35ebcd308e93
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20251210085744-f8481d179ac5
github.com/jfrog/jfrog-cli-security v1.24.2
github.com/jfrog/jfrog-cli-security v1.25.0
github.com/jfrog/jfrog-client-go v1.55.1-0.20251217080430-c92b763b7465
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/owenrumney/go-sarif/v3 v3.2.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251211075913-35ebcd308e93 h1:r
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251211075913-35ebcd308e93/go.mod h1:7cCaRhXorlbyXZgiW5bplCExFxlnROaG21K12d8inpQ=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20251210085744-f8481d179ac5 h1:GYE67ubwl+ZRw3CcXFUi49EwwQp6k+qS8sX0QuHDHO8=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20251210085744-f8481d179ac5/go.mod h1:BMoGi2rG0udCCeaghqlNgiW3fTmT+TNnfTnBoWFYgcg=
github.com/jfrog/jfrog-cli-security v1.24.2 h1:nyI0lNYR8i6yZYeBDsBJnURYsMnFKEmt7QH4vaNxtGM=
github.com/jfrog/jfrog-cli-security v1.24.2/go.mod h1:3FXD5IkKtdQOm9CZk6cR7q0iC6PaGMnjqzZqRcQp2r0=
github.com/jfrog/jfrog-cli-security v1.25.0 h1:DM29QsMkFLRD6adKCWISe3uHFVaOodIN56NH7ThpKKU=
github.com/jfrog/jfrog-cli-security v1.25.0/go.mod h1:IV/+JhaLmyeMb8IAoJZYUq4gONW5BdrxgdT4w7SXgz0=
github.com/jfrog/jfrog-client-go v1.55.1-0.20251217080430-c92b763b7465 h1:Ff3BlNPndrAfa1xFI/ORFzfWTxQxF0buWG61PEJwd3U=
github.com/jfrog/jfrog-client-go v1.55.1-0.20251217080430-c92b763b7465/go.mod h1:WQ5Y+oKYyHFAlCbHN925bWhnShTd2ruxZ6YTpb76fpU=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
Expand Down
9 changes: 7 additions & 2 deletions packagehandlers/commonpackagehandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ func getFixedPackage(impactedPackage string, versionOperator string, suggestedFi
return
}

// Recursively scans the current directory for descriptor files based on the provided list of suffixes, while excluding paths that match the specified exclusion patterns.
// Scans the current directory for descriptor files based on the provided list of suffixes, while excluding paths that match the specified exclusion patterns.
// Only scans the current directory level (non-recursive) to avoid conflicts with auto-detected subdirectory targets that will be processed separately.
// The patternsToExclude must be provided as regexp patterns. For instance, if the pattern ".*node_modules.*" is provided, any paths containing "node_modules" will be excluded from the result.
// Returns a slice of all discovered descriptor files, represented as absolute paths.
// Returns a slice of all discovered descriptor files in the current directory, represented as absolute paths.
func (cph *CommonPackageHandler) GetAllDescriptorFilesFullPaths(descriptorFilesSuffixes []string, patternsToExclude ...string) (descriptorFilesFullPaths []string, err error) {
if len(descriptorFilesSuffixes) == 0 {
return
Expand All @@ -111,6 +112,10 @@ func (cph *CommonPackageHandler) GetAllDescriptorFilesFullPaths(descriptorFilesS
return fmt.Errorf("an error has occurred when attempting to access or traverse the file system: %w", innerErr)
}

if d.IsDir() && path != "." {
return filepath.SkipDir
}

for _, regexpCompiler := range regexpPatternsCompilers {
if match := regexpCompiler.FindString(path); match != "" {
return filepath.SkipDir
Expand Down
4 changes: 2 additions & 2 deletions packagehandlers/packagehandlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ func TestUpdateDependency(t *testing.T) {
},
scanDetails: scanDetails,
fixSupported: true,
descriptorsToCheck: []string{"build.gradle", filepath.Join("innerProjectForTest", "build.gradle.kts")},
descriptorsToCheck: []string{"build.gradle"},
},
},

Expand Down Expand Up @@ -966,7 +966,7 @@ func TestGetAllDescriptorFilesFullPaths(t *testing.T) {
{
testProjectRepo: "gradle",
suffixesToSearch: []string{groovyDescriptorFileSuffix, kotlinDescriptorFileSuffix},
expectedResultSuffixes: []string{filepath.Join("innerProjectForTest", "build.gradle.kts"), "build.gradle"},
expectedResultSuffixes: []string{"build.gradle"},
},
// This test case verifies that paths containing excluded patterns are omitted from the output
{
Expand Down
74 changes: 49 additions & 25 deletions scanrepository/scanrepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Reposito
}

func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) (int, error) {
var fixNeeded bool
var isFixNeeded bool
totalFindings := 0
// A map that contains the full project paths as a keys
// The value is a map of vulnerable package names -> the scanDetails of the vulnerable packages.
Expand Down Expand Up @@ -203,22 +203,39 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) (i
if repository.DetectionOnly {
continue
}
// Prepare the vulnerabilities map for each working dir path
currPathVulnerabilities, err := cfp.getVulnerabilitiesMap(scanResults)
if err != nil {
if err = utils.CreateErrorIfPartialResultsDisabled(cfp.scanDetails.AllowPartialResults(), fmt.Sprintf("An error occurred while preparing the vulnerabilities map for '%s' working directory. Fixes will be skipped for this working directory", fullPathWd), err); err != nil {
return totalFindings, err

for _, target := range scanResults.Targets {
targetPath := target.Target
convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{
IncludeVulnerabilities: scanResults.IncludesVulnerabilities(),
HasViolationContext: scanResults.HasViolationContext(),
IncludeTargets: []string{targetPath},
})
simpleJsonResult, err := convertor.ConvertToSimpleJson(scanResults)
if err != nil {
if err = utils.CreateErrorIfPartialResultsDisabled(cfp.scanDetails.AllowPartialResults(), fmt.Sprintf("An error occurred while preparing the vulnerabilities map for '%s' working directory. Fixes will be skipped for this working directory", targetPath), err); err != nil {
return totalFindings, err
}
continue
}
continue
}
if len(currPathVulnerabilities) > 0 {
fixNeeded = true

currPathVulnerabilities, err := cfp.createVulnerabilitiesMapFromSimpleJson(simpleJsonResult)
if err != nil {
if err = utils.CreateErrorIfPartialResultsDisabled(cfp.scanDetails.AllowPartialResults(), fmt.Sprintf("An error occurred while preparing the vulnerabilities map for '%s' working directory. Fixes will be skipped for this working directory", targetPath), err); err != nil {
return totalFindings, err
}
continue
}
if len(currPathVulnerabilities) > 0 {
isFixNeeded = true
log.Debug(fmt.Sprintf("Found %d fixable vulnerabilities in '%s': %s", len(currPathVulnerabilities), targetPath, strings.Join(maps.Keys(currPathVulnerabilities), ", ")))
}
vulnerabilitiesByPathMap[targetPath] = currPathVulnerabilities
}
vulnerabilitiesByPathMap[fullPathWd] = currPathVulnerabilities
}
if repository.DetectionOnly {
log.Info(fmt.Sprintf("This command is running in detection mode only. To enable automatic fixing of issues, set the '%s' environment variable to 'false'.", utils.DetectionOnlyEnv))
} else if fixNeeded {
} else if isFixNeeded {
return totalFindings, cfp.fixVulnerablePackages(repository, vulnerabilitiesByPathMap)
}
return totalFindings, nil
Expand All @@ -237,19 +254,6 @@ func (cfp *ScanRepositoryCmd) scan(currentWorkingDir string) (*results.SecurityC
return auditResults, nil
}

func (cfp *ScanRepositoryCmd) getVulnerabilitiesMap(scanResults *results.SecurityCommandResults) (map[string]*utils.VulnerabilityDetails, error) {
vulnerabilitiesMap, err := cfp.createVulnerabilitiesMap(scanResults)
if err != nil {
return nil, err
}

// Nothing to fix, return
if len(vulnerabilitiesMap) == 0 {
log.Info("Didn't find vulnerable dependencies with existing fix versions for", cfp.scanDetails.RepoName)
}
return vulnerabilitiesMap, nil
}

func (cfp *ScanRepositoryCmd) fixVulnerablePackages(repository *utils.Repository, vulnerabilitiesByWdMap map[string]map[string]*utils.VulnerabilityDetails) (err error) {
if cfp.aggregateFixes {
err = cfp.fixIssuesSinglePR(repository, vulnerabilitiesByWdMap)
Expand Down Expand Up @@ -598,6 +602,26 @@ func (cfp *ScanRepositoryCmd) createVulnerabilitiesMap(scanResults *results.Secu
return vulnerabilitiesMap, nil
}

func (cfp *ScanRepositoryCmd) createVulnerabilitiesMapFromSimpleJson(simpleJsonResult formats.SimpleJsonResults) (map[string]*utils.VulnerabilityDetails, error) {
vulnerabilitiesMap := map[string]*utils.VulnerabilityDetails{}
var err error

if len(simpleJsonResult.Vulnerabilities) > 0 {
for i := range simpleJsonResult.Vulnerabilities {
if err = cfp.addVulnerabilityToFixVersionsMap(&simpleJsonResult.Vulnerabilities[i], vulnerabilitiesMap); err != nil {
return nil, err
}
}
} else if len(simpleJsonResult.SecurityViolations) > 0 {
for i := range simpleJsonResult.SecurityViolations {
if err = cfp.addVulnerabilityToFixVersionsMap(&simpleJsonResult.SecurityViolations[i], vulnerabilitiesMap); err != nil {
return nil, err
}
}
}
return vulnerabilitiesMap, nil
}

func (cfp *ScanRepositoryCmd) addVulnerabilityToFixVersionsMap(vulnerability *formats.VulnerabilityOrViolationRow, vulnerabilitiesMap map[string]*utils.VulnerabilityDetails) error {
if len(vulnerability.FixedVersions) == 0 {
return nil
Expand Down
69 changes: 69 additions & 0 deletions scanrepository/scanrepository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/jfrog/jfrog-cli-security/utils/formats"
"github.com/jfrog/jfrog-cli-security/utils/formats/violationutils"
"github.com/jfrog/jfrog-cli-security/utils/results"
"github.com/jfrog/jfrog-cli-security/utils/results/conversion"
"github.com/jfrog/jfrog-cli-security/utils/severityutils"
"github.com/jfrog/jfrog-cli-security/utils/techutils"
"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
Expand Down Expand Up @@ -696,6 +697,74 @@ func TestCreateVulnerabilitiesMap(t *testing.T) {
}
}

func TestMultiTargetVulnerabilitiesMap(t *testing.T) {
cfp := &ScanRepositoryCmd{}

scanResults := &results.SecurityCommandResults{
ResultsMetaData: results.ResultsMetaData{ResultContext: results.ResultContext{IncludeVulnerabilities: true}},
Targets: []*results.TargetResults{
{
ScanTarget: results.ScanTarget{Target: "project1"},
ScaResults: &results.ScaScanResults{
DeprecatedXrayResults: []services.ScanResponse{{
Vulnerabilities: []services.Vulnerability{{
Cves: []services.Cve{{Id: "CVE-1"}},
Severity: "High",
Components: map[string]services.Component{
"pkg1": {
FixedVersions: []string{"1.0.0"},
ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "pkg1"}}},
},
},
}},
}},
},
},
{
ScanTarget: results.ScanTarget{Target: "project2"},
ScaResults: &results.ScaScanResults{
DeprecatedXrayResults: []services.ScanResponse{{
Vulnerabilities: []services.Vulnerability{{
Cves: []services.Cve{{Id: "CVE-2"}},
Severity: "Critical",
Components: map[string]services.Component{
"pkg2": {
FixedVersions: []string{"2.0.0"},
ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "pkg2"}}},
},
},
}},
}},
},
},
},
}

convertor1 := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{
IncludeVulnerabilities: true,
IncludeTargets: []string{"project1"},
})
simpleJson1, err := convertor1.ConvertToSimpleJson(scanResults)
assert.NoError(t, err)
vulnsMap1, err := cfp.createVulnerabilitiesMapFromSimpleJson(simpleJson1)
assert.NoError(t, err)
assert.Len(t, vulnsMap1, 1)
assert.Contains(t, vulnsMap1, "pkg1")
assert.NotContains(t, vulnsMap1, "pkg2")

convertor2 := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{
IncludeVulnerabilities: true,
IncludeTargets: []string{"project2"},
})
simpleJson2, err := convertor2.ConvertToSimpleJson(scanResults)
assert.NoError(t, err)
vulnsMap2, err := cfp.createVulnerabilitiesMapFromSimpleJson(simpleJson2)
assert.NoError(t, err)
assert.Len(t, vulnsMap2, 1)
assert.Contains(t, vulnsMap2, "pkg2")
assert.NotContains(t, vulnsMap2, "pkg1")
}

// Verifies unsupported packages return specific error
// Other logic is implemented inside each package-handler.
func TestUpdatePackageToFixedVersion(t *testing.T) {
Expand Down
Loading