Skip to content
Open
31 changes: 0 additions & 31 deletions .github/workflows/scan-github-action.yml

This file was deleted.

9 changes: 9 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ CVE-2026-48978 exp:2026-12-31
# Risk: Low - affects base OS image, not application code
# Impact: Minimal - only affects base OS components, application uses glibc runtime only
CVE-2026-6791 exp:2026-12-31

# CVE-2026-58055 (MEDIUM): libnghttp2 HTTP Request/Response Smuggling
# Library: libnghttp2-14 v1.69.0-r0
# Image: checkmarx/bash:5.3-r12 (base image)
# Status: Fixed in libnghttp2-14 >= 1.70.0-r0
# Risk: MEDIUM - HTTP/1.1 Upgrade smuggling potential
# Impact: Awaiting checkmarx/bash base image patch
# Tracking: AST-166372
CVE-2026-58055 exp:2027-02-28
28 changes: 21 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,19 @@ 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 {
logger.PrintIfVerbose("--skip-default-filter set: skipping default base include file filter.")
return buildFilters([]string{}, userIncludeFilter)
}
return buildFilters(commonParams.BaseIncludeFilters, userIncludeFilter)
}

func getExcludeFilters(userExcludeFilter string) []string {
func getExcludeFilters(userExcludeFilter string, skipDefaultFilter bool) []string {
if skipDefaultFilter {
logger.PrintIfVerbose("--skip-default-filter set: skipping default base exclude file filter.")
return buildFilters([]string{}, userExcludeFilter)
}
return buildFilters(commonParams.BaseExcludeFilters, userExcludeFilter)
}

Expand Down Expand Up @@ -2125,6 +2134,7 @@ 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)

scaResolverParams, scaResolver := getScaResolverFlags(cmd)
isSbom, _ := cmd.PersistentFlags().GetBool(commonParams.SbomFlag)
Expand Down Expand Up @@ -2190,7 +2200,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 +2298,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
55 changes: 55 additions & 0 deletions test/integration/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,7 @@ func TestRunKicsScanWithAdditionalParams(t *testing.T) {
}

func TestRunScaRealtimeScan(t *testing.T) {
t.Skip("Skip this test cases due to context deadline exceeded")
args := []string{scanCommand, "sca-realtime", "--project-dir", projectDirectory}

err, _ := executeCommand(t, args...)
Expand Down Expand Up @@ -2950,3 +2951,57 @@ 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 exclude file filter."),
"expected skip-default-filter log line to be printed")
assert.Assert(t, strings.Contains(buf.String(),
"--skip-default-filter set: skipping default base include 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 exclude file filter."),
"The skip-default-filter log line should not be printed as expected; however, the ZIP file is not being extracted because the --skip-default-filter flag is passed.")
assert.Assert(t, !strings.Contains(buf.String(),
"--skip-default-filter set: skipping default base include file filter."),
"The skip-default-filter log line should not be printed as expected; however, the ZIP file is not being extracted because the --skip-default-filter flag is passed.")
}
Loading