Skip to content
Open
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
29 changes: 22 additions & 7 deletions internal/commands/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@ func scanCreateSubCommand(
createScanCmd.PersistentFlags().Bool(commonParams.NoScanFlag, false, "Prevents CxOne scan from running after SBOM is generated locally. Relevant only when --sbom-first is submitted under --sca-resolver-params. Submitting this flag without --sbom-first causes an error.")
createScanCmd.PersistentFlags().Bool(commonParams.GitIgnoreFileFilterFlag, false, commonParams.GitIgnoreFileFilterUsage)
createScanCmd.PersistentFlags().StringSlice(commonParams.AntFilterFlag, []string{}, commonParams.AntFilterUsage)
createScanCmd.PersistentFlags().Bool(commonParams.SkipDefaultFilterFlag, false, commonParams.SkipDefaultFilterFlagUsage)

return createScanCmd
}
Expand Down Expand Up @@ -1643,7 +1644,7 @@ func scanTypeEnabled(scanType string) bool {
return false
}

func compressFolder(sourceDir, filter, userIncludeFilter, scaResolver string, antMatcher filtering.Matcher) (string, error) {
func compressFolder(sourceDir, filter, userIncludeFilter, scaResolver string, antMatcher filtering.Matcher, skipDefaultFilter bool) (string, error) {
scaToolPath := scaResolver
outputFile, err := os.CreateTemp(os.TempDir(), "cx-*.zip")
if err != nil {
Expand All @@ -1653,7 +1654,7 @@ func compressFolder(sourceDir, filter, userIncludeFilter, scaResolver string, an
zipWriter := zip.NewWriter(outputFile)

// First check if the directory is empty or all files are filtered out
isEmpty, err := isDirEmpty(sourceDir, getExcludeFilters(filter), getIncludeFilters(userIncludeFilter), antMatcher)
isEmpty, err := isDirEmpty(sourceDir, getExcludeFilters(filter, skipDefaultFilter), getIncludeFilters(userIncludeFilter, skipDefaultFilter), antMatcher)
if err != nil {
return "", err
}
Expand All @@ -1671,7 +1672,7 @@ func compressFolder(sourceDir, filter, userIncludeFilter, scaResolver string, an
}
} else {
// Add directory files normally
err = addDirFiles(zipWriter, "", sourceDir, getExcludeFilters(filter), getIncludeFilters(userIncludeFilter), antMatcher)
err = addDirFiles(zipWriter, "", sourceDir, getExcludeFilters(filter, skipDefaultFilter), getIncludeFilters(userIncludeFilter, skipDefaultFilter), antMatcher)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -1752,11 +1753,17 @@ func isDirEmpty(dir string, excludeFilters, includeFilters []string, antMatcher
return empty, err
}

func getIncludeFilters(userIncludeFilter string) []string {
func getIncludeFilters(userIncludeFilter string, skipDefaultFilter bool) []string {
if skipDefaultFilter {
return buildFilters([]string{}, userIncludeFilter)
}
return buildFilters(commonParams.BaseIncludeFilters, userIncludeFilter)
}

func getExcludeFilters(userExcludeFilter string) []string {
func getExcludeFilters(userExcludeFilter string, skipDefaultFilter bool) []string {
if skipDefaultFilter {
return buildFilters([]string{}, userExcludeFilter)
}
return buildFilters(commonParams.BaseExcludeFilters, userExcludeFilter)
}

Expand Down Expand Up @@ -2125,6 +2132,10 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
containerImagesFlag, _ := cmd.Flags().GetString(commonParams.ContainerImagesFlag)
containerResolveLocally, _ := cmd.Flags().GetBool(commonParams.ContainerResolveLocallyFlag)
scaResolverPath, _ := cmd.Flags().GetString(commonParams.ScaResolverFlag)
skipDefaultFilter, _ := cmd.Flags().GetBool(commonParams.SkipDefaultFilterFlag)
if skipDefaultFilter {
logger.PrintIfVerbose("--skip-default-filter set: skipping default base include/exclude file filter.")
}

scaResolverParams, scaResolver := getScaResolverFlags(cmd)
isSbom, _ := cmd.PersistentFlags().GetBool(commonParams.SbomFlag)
Expand Down Expand Up @@ -2190,7 +2201,11 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
var errorUnzippingFile error
userProvidedZip := len(zipFilePath) > 0

unzip := ((sourceDirFilter != "" || userIncludeFilter != "" || len(antPatterns) > 0) || containerScanTriggered) && userProvidedZip
// containerScanTriggered must stay in this condition: without it, a container scan
// run with --containers-local-resolution and --skip-default-filter (and no
// --file-filter/--file-include) would never unzip the zip source, so local container
// resolution would never run. Keeping it here ensures the zip is still unzipped in that case.
unzip := ((sourceDirFilter != "" || userIncludeFilter != "" || len(antPatterns) > 0) || containerScanTriggered || !skipDefaultFilter) && userProvidedZip
if unzip {
directoryPath, errorUnzippingFile = UnzipFile(zipFilePath)
if errorUnzippingFile != nil {
Expand Down Expand Up @@ -2284,7 +2299,7 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
}
} else {
if !isSbom {
zipFilePath, dirPathErr = compressFolder(directoryPath, sourceDirFilter, userIncludeFilter, scaResolver, antMatcher)
zipFilePath, dirPathErr = compressFolder(directoryPath, sourceDirFilter, userIncludeFilter, scaResolver, antMatcher, skipDefaultFilter)
}

// Clean up .checkmarx/containers directory after successful mixed scan (including containers) compression
Expand Down
145 changes: 141 additions & 4 deletions internal/commands/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"path/filepath"
"reflect"
"slices"
"strings"
"testing"

Expand Down Expand Up @@ -5346,7 +5347,7 @@ func TestSbomFileExcludedFromZip_WithCustomOutputName(t *testing.T) {

noopMatcher, matcherErr := filtering.NewAntMatcher(nil)
assert.NilError(t, matcherErr)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher, false)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

Expand Down Expand Up @@ -5377,7 +5378,7 @@ func TestDefaultSbomFileAlwaysExcludedFromZip(t *testing.T) {

noopMatcher, matcherErr := filtering.NewAntMatcher(nil)
assert.NilError(t, matcherErr)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher, false)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

Expand Down Expand Up @@ -5410,7 +5411,7 @@ func TestSbomFileExcludedFromZip_InSubdirectory(t *testing.T) {

noopMatcher, matcherErr := filtering.NewAntMatcher(nil)
assert.NilError(t, matcherErr)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher, false)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

Expand Down Expand Up @@ -5449,7 +5450,7 @@ func TestSbomFileExcludedFromZip_AbsoluteSubdirWithCustomName(t *testing.T) {

noopMatcher, matcherErr := filtering.NewAntMatcher(nil)
assert.NilError(t, matcherErr)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher, false)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

Expand Down Expand Up @@ -5505,3 +5506,139 @@ func cleanupMockAccessToken() {
// Reset to default value (300 seconds as per params/binds.go)
viper.Set(commonParams.TokenExpirySecondsKey, 300)
}

// --skip-default-filter tests

func TestGetFilters_SkipDefaultFilter(t *testing.T) {
assert.DeepEqual(t, getIncludeFilters("*.foo", true), []string{"*.foo"})
assert.DeepEqual(t, getExcludeFilters("!bar", true), []string{"!bar"})

includeDefault := getIncludeFilters("*.foo", false)
assert.Assert(t, slices.Contains(includeDefault, "*.go"))
assert.Assert(t, slices.Contains(includeDefault, "*.foo"))

excludeDefault := getExcludeFilters("!bar", false)
assert.Assert(t, slices.Contains(excludeDefault, "!node_modules"))
assert.Assert(t, slices.Contains(excludeDefault, "!bar"))
}

func TestCompressFolder_DefaultBehaviorUnchanged(t *testing.T) {
projectDir, err := os.MkdirTemp("", "skip-default-filter-off-*")
assert.NilError(t, err)
defer func() { _ = os.RemoveAll(projectDir) }()

assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "main.go"), []byte("package main"), 0600))
assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "asset.bin"), []byte("binary"), 0600))
nodeModulesDir := filepath.Join(projectDir, "node_modules")
assert.NilError(t, os.MkdirAll(nodeModulesDir, 0700))
assert.NilError(t, os.WriteFile(filepath.Join(nodeModulesDir, "lib.js"), []byte("//lib"), 0600))

noopMatcher, matcherErr := filtering.NewAntMatcher(nil)
assert.NilError(t, matcherErr)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher, false)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

assert.Equal(t, true, zipContainsFile(t, zipPath, "main.go"))
assert.Equal(t, false, zipContainsFile(t, zipPath, "asset.bin"))
assert.Equal(t, false, zipContainsFile(t, zipPath, "lib.js"))
}

func TestCompressFolder_SkipDefaultFilter(t *testing.T) {
projectDir, err := os.MkdirTemp("", "skip-default-filter-on-*")
assert.NilError(t, err)
defer func() { _ = os.RemoveAll(projectDir) }()

assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "asset.bin"), []byte("binary"), 0600))
nodeModulesDir := filepath.Join(projectDir, "node_modules")
assert.NilError(t, os.MkdirAll(nodeModulesDir, 0700))
assert.NilError(t, os.WriteFile(filepath.Join(nodeModulesDir, "lib.js"), []byte("//lib"), 0600))

noopMatcher, matcherErr := filtering.NewAntMatcher(nil)
assert.NilError(t, matcherErr)
zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", noopMatcher, true)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

assert.Equal(t, true, zipContainsFile(t, zipPath, "asset.bin"))
assert.Equal(t, true, zipContainsFile(t, zipPath, "lib.js"))
}

func TestCreateScanSkipDefaultFilter_Wiring(t *testing.T) {
execCmdNilAssertion(t,
"scan", "create", "--project-name", "MOCK", "-s", "data", "-b", "dummy_branch",
"--skip-default-filter",
)
}

// skip-default-filter bypasses base filters, ant exclude pattern still applies.
func TestCompressFolder_SkipDefaultFilter_WithAntFilterExclude(t *testing.T) {
projectDir, err := os.MkdirTemp("", "skip-default-filter-ant-exclude-*")
assert.NilError(t, err)
defer func() { _ = os.RemoveAll(projectDir) }()

assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "main.go"), []byte("package main"), 0600))
assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "asset.customext"), []byte("data"), 0600))
excludedDir := filepath.Join(projectDir, "excluded_by_ant")
assert.NilError(t, os.MkdirAll(excludedDir, 0700))
assert.NilError(t, os.WriteFile(filepath.Join(excludedDir, "marker.go"), []byte("package excluded"), 0600))

antMatcher, matcherErr := filtering.NewAntMatcher([]string{"!excluded_by_ant/**"})
assert.NilError(t, matcherErr)

zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", antMatcher, true)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

assert.Equal(t, true, zipContainsFile(t, zipPath, "main.go"))
assert.Equal(t, true, zipContainsFile(t, zipPath, "asset.customext"))
assert.Equal(t, false, zipContainsFile(t, zipPath, "marker.go"))
}

// skip-default-filter with an ant include-only pattern drops non-matching files too.
func TestCompressFolder_SkipDefaultFilter_WithAntFilterIncludeOnly(t *testing.T) {
projectDir, err := os.MkdirTemp("", "skip-default-filter-ant-include-*")
assert.NilError(t, err)
defer func() { _ = os.RemoveAll(projectDir) }()

assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "main.go"), []byte("package main"), 0600))
assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "asset.customext"), []byte("data"), 0600))

antMatcher, matcherErr := filtering.NewAntMatcher([]string{"**/*.customext"})
assert.NilError(t, matcherErr)

zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", antMatcher, true)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

assert.Equal(t, true, zipContainsFile(t, zipPath, "asset.customext"))
assert.Equal(t, false, zipContainsFile(t, zipPath, "main.go"))
}

// file-filter-ext without skip-default-filter: base filters and the ant filter both apply.
func TestCompressFolder_DefaultFilters_WithAntFilter(t *testing.T) {
projectDir, err := os.MkdirTemp("", "default-filter-with-ant-*")
assert.NilError(t, err)
defer func() { _ = os.RemoveAll(projectDir) }()

assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "main.go"), []byte("package main"), 0600))
assert.NilError(t, os.WriteFile(filepath.Join(projectDir, "asset.customext"), []byte("data"), 0600))
nodeModulesDir := filepath.Join(projectDir, "node_modules")
assert.NilError(t, os.MkdirAll(nodeModulesDir, 0700))
assert.NilError(t, os.WriteFile(filepath.Join(nodeModulesDir, "lib.js"), []byte("//lib"), 0600))
keepDir := filepath.Join(projectDir, "keep_dir")
assert.NilError(t, os.MkdirAll(keepDir, 0700))
assert.NilError(t, os.WriteFile(filepath.Join(keepDir, "marker.go"), []byte("package keep"), 0600))

antMatcher, matcherErr := filtering.NewAntMatcher([]string{"!keep_dir/**"})
assert.NilError(t, matcherErr)

zipPath, err := compressFolder(sbomTestSourceDir(projectDir), "", "", "", antMatcher, false)
assert.NilError(t, err)
defer func() { _ = os.Remove(zipPath) }()

assert.Equal(t, true, zipContainsFile(t, zipPath, "main.go"))
assert.Equal(t, false, zipContainsFile(t, zipPath, "asset.customext"))
assert.Equal(t, false, zipContainsFile(t, zipPath, "lib.js"))
assert.Equal(t, false, zipContainsFile(t, zipPath, "marker.go"))
}
2 changes: 2 additions & 0 deletions internal/params/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ const (
LogFileUsage = "Saves logs to the specified file path only"
LogFileConsoleFlag = "log-file-console"
LogFileConsoleUsage = "Saves logs to the specified file path as well as to the console"
SkipDefaultFilterFlag = "skip-default-filter"
SkipDefaultFilterFlagUsage = "Skip the default file filter."
GitIgnoreFileFilterFlag = "use-gitignore"
GitIgnoreFileFilterUsage = "Exclude files and directories from the scan based on the patterns defined in the directory's .gitignore file"
AntFilterFlag = "file-filter-ext"
Expand Down
48 changes: 48 additions & 0 deletions test/integration/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2950,3 +2950,51 @@ func TestScanCreateIncludeFilterIsCaseInsensitive(t *testing.T) {
"uppercase --file-include pattern *.TXT should still match lowercase .txt files on disk",
)
}

// Directory source with --skip-default-filter should scan successfully
func TestScanCreateSkipDefaultFilterDirectory(t *testing.T) {
args := []string{
"scan", "create",
flag(params.ProjectName), getProjectNameForScanTests(),
flag(params.SourcesFlag), Dir,
flag(params.ScanTypes), params.SastType,
flag(params.SkipDefaultFilterFlag),
flag(params.BranchFlag), "dummy_branch",
flag(params.DebugFlag),
}

// Capture log output to assert the skip-default-filter code path actually ran
var buf bytes.Buffer
log.SetOutput(&buf)
defer log.SetOutput(os.Stderr)

executeCmdWithTimeOutNilAssertion(t, "Skip default filter scan should complete", timeout, args...)

assert.Assert(t, strings.Contains(buf.String(),
"--skip-default-filter set: skipping default base include/exclude file filter."),
"expected skip-default-filter log line to be printed")
}

// Zip source with --skip-default-filter should scan successfully
func TestScanCreateSkipDefaultFilterZip(t *testing.T) {
args := []string{
"scan", "create",
flag(params.ProjectName), getProjectNameForScanTests(),
flag(params.SourcesFlag), Zip,
flag(params.ScanTypes), params.SastType,
flag(params.SkipDefaultFilterFlag),
flag(params.BranchFlag), "dummy_branch",
flag(params.DebugFlag),
}

// Capture log output to assert the skip-default-filter code path actually ran
var buf bytes.Buffer
log.SetOutput(&buf)
defer log.SetOutput(os.Stderr)

executeCmdWithTimeOutNilAssertion(t, "Skip default filter zip scan should complete", timeout, args...)

assert.Assert(t, strings.Contains(buf.String(),
"--skip-default-filter set: skipping default base include/exclude file filter."),
"expected skip-default-filter log line to be printed")
}
Loading