From 75d3ddca41757715632757a359f2264b83165875 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:04:02 +0530 Subject: [PATCH 01/16] Code changes done to get license details --- internal/commands/root.go | 1 + internal/commands/util/tenant.go | 29 ++++++++++++++++++--- internal/commands/util/tenant_test.go | 12 ++++----- internal/commands/util/utils.go | 3 ++- internal/commands/util/utils_test.go | 1 + internal/wrappers/jwt-helper.go | 31 +++++++++++++++++++++++ internal/wrappers/mock/jwt-helper-mock.go | 15 +++++++++++ 7 files changed, 81 insertions(+), 11 deletions(-) diff --git a/internal/commands/root.go b/internal/commands/root.go index 9c56cb812..e2c531af7 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -215,6 +215,7 @@ func NewAstCLI( prWrapper, learnMoreWrapper, tenantWrapper, + jwtWrapper, chatWrapper, policyWrapper, scansWrapper, diff --git a/internal/commands/util/tenant.go b/internal/commands/util/tenant.go index fde007b27..62b9df641 100644 --- a/internal/commands/util/tenant.go +++ b/internal/commands/util/tenant.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -func NewTenantConfigurationCommand(wrapper wrappers.TenantConfigurationWrapper) *cobra.Command { +func NewTenantConfigurationCommand(wrapper wrappers.TenantConfigurationWrapper, jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) *cobra.Command { cmd := &cobra.Command{ Use: "tenant", Short: "Shows the tenant settings", @@ -27,7 +27,7 @@ func NewTenantConfigurationCommand(wrapper wrappers.TenantConfigurationWrapper) `, ), }, - RunE: runTenantCmd(wrapper), + RunE: runTenantCmd(wrapper, jwtWrapper, featureFlagsWrapper), } cmd.PersistentFlags().String( params.FormatFlag, @@ -40,7 +40,7 @@ func NewTenantConfigurationCommand(wrapper wrappers.TenantConfigurationWrapper) return cmd } -func runTenantCmd(wrapper wrappers.TenantConfigurationWrapper) func(cmd *cobra.Command, args []string) error { +func runTenantCmd(wrapper wrappers.TenantConfigurationWrapper, jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { tenantConfigurationResponse, errorModel, err := wrapper.GetTenantConfiguration() if err != nil { @@ -52,10 +52,17 @@ func runTenantCmd(wrapper wrappers.TenantConfigurationWrapper) func(cmd *cobra.C if tenantConfigurationResponse != nil { format, _ := cmd.Flags().GetString(params.FormatFlag) tenantConfigurationResponseView := toTenantConfigurationResponseView(tenantConfigurationResponse) + + // Get license details and add them to the response + licenseDetails, err := jwtWrapper.GetLicenseDetails(featureFlagsWrapper) + if err == nil { + tenantConfigurationResponseView = appendLicenseDetails(tenantConfigurationResponseView, licenseDetails) + } + if format == "" { format = defaultFormat } - err := printer.Print(cmd.OutOrStdout(), tenantConfigurationResponseView, format) + err = printer.Print(cmd.OutOrStdout(), tenantConfigurationResponseView, format) if err != nil { return err } @@ -76,3 +83,17 @@ func toTenantConfigurationResponseView(response *[]*wrappers.TenantConfiguration } return tenantConfigurationResponseView } + +func appendLicenseDetails(responseView interface{}, licenseDetails map[string]string) interface{} { + tenantConfigurationResponseView := responseView.([]*wrappers.TenantConfigurationResponse) + + for key, value := range licenseDetails { + licenseEntry := &wrappers.TenantConfigurationResponse{ + Key: key, + Value: value, + } + tenantConfigurationResponseView = append(tenantConfigurationResponseView, licenseEntry) + } + + return tenantConfigurationResponseView +} diff --git a/internal/commands/util/tenant_test.go b/internal/commands/util/tenant_test.go index 3cf8313f1..67b1a7271 100644 --- a/internal/commands/util/tenant_test.go +++ b/internal/commands/util/tenant_test.go @@ -8,41 +8,41 @@ import ( ) func TestTenantConfigurationHelp(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--help"}) err := cmd.Execute() assert.Assert(t, err == nil) } func TestTenantConfigurationJsonFormat(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--format", "json"}) err := cmd.Execute() assert.NilError(t, err, "Tenant configuration command should run with no errors and print to json") } func TestTenantConfigurationListFormat(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--format", "list"}) err := cmd.Execute() assert.NilError(t, err, "Tenant configuration command should run with no errors and print to list") } func TestTenantConfigurationTableFormat(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--format", "table"}) err := cmd.Execute() assert.NilError(t, err, "Tenant configuration command should run with no errors and print to table") } func TestTenantConfigurationInvalidFormat(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--format", "MOCK"}) err := cmd.Execute() assert.Assert(t, err.Error() == mockFormatErrorMessage) } func TestNewTenantConfigurationCommand(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) assert.Assert(t, cmd != nil, "Tenant configuration command must exist") } diff --git a/internal/commands/util/utils.go b/internal/commands/util/utils.go index 15413b240..3b7285aa8 100644 --- a/internal/commands/util/utils.go +++ b/internal/commands/util/utils.go @@ -35,6 +35,7 @@ func NewUtilsCommand( prWrapper wrappers.PRWrapper, learnMoreWrapper wrappers.LearnMoreWrapper, tenantWrapper wrappers.TenantConfigurationWrapper, + jwtWrapper wrappers.JWTWrapper, chatWrapper wrappers.ChatWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper, @@ -76,7 +77,7 @@ func NewUtilsCommand( learnMoreCmd := NewLearnMoreCommand(learnMoreWrapper) - tenantCmd := NewTenantConfigurationCommand(tenantWrapper) + tenantCmd := NewTenantConfigurationCommand(tenantWrapper, jwtWrapper, featureFlagsWrapper) maskSecretsCmd := NewMaskSecretsCommand(chatWrapper) diff --git a/internal/commands/util/utils_test.go b/internal/commands/util/utils_test.go index 9e390c804..1a422a4bd 100644 --- a/internal/commands/util/utils_test.go +++ b/internal/commands/util/utils_test.go @@ -22,6 +22,7 @@ func TestNewUtilsCommand(t *testing.T) { nil, mock.LearnMoreMockWrapper{}, mock.TenantConfigurationMockWrapper{}, + &mock.JWTMockWrapper{}, mock.ChatMockWrapper{}, nil, nil, diff --git a/internal/wrappers/jwt-helper.go b/internal/wrappers/jwt-helper.go index 7a3ec506f..b42561804 100644 --- a/internal/wrappers/jwt-helper.go +++ b/internal/wrappers/jwt-helper.go @@ -23,6 +23,7 @@ type JWTStruct struct { type JWTWrapper interface { GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (allowedEngines map[string]bool, err error) + GetLicenseDetails(featureFlagsWrapper FeatureFlagsWrapper) (licenseDetails map[string]string, err error) IsAllowedEngine(engine string) (bool, error) ExtractTenantFromToken() (tenant string, err error) CheckPermissionByAccessToken(requiredPermission string) (permission bool, err error) @@ -76,6 +77,36 @@ func (*JWTStruct) GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (al return getDefaultEngines(scsLicensingV2Flag.Status), nil } +// GetLicenseDetails will return all license information from AstLicense.LicenseData.AllowedEngines +func (*JWTStruct) GetLicenseDetails(featureFlagsWrapper FeatureFlagsWrapper) (licenseDetails map[string]string, err error) { + //flagResponse, _ := GetSpecificFeatureFlag(featureFlagsWrapper, PackageEnforcementEnabled) + + licenseDetails = make(map[string]string) + + // Add scan.config.plugins.standalone as true + licenseDetails["scan.config.plugins.standalone"] = "true" + + // if flagResponse.Status { + // jwtStruct, err := getJwtStruct() + // if err != nil { + // return licenseDetails, nil // Return what we have so far if JWT validation fails + // } + + // // Return all engines from license data as true (they are allowed engines) + // if jwtStruct.AstLicense.LicenseData.AllowedEngines != nil { + // for _, engine := range jwtStruct.AstLicense.LicenseData.AllowedEngines { + // if engine != "" { + // licenseDetails[engine] = "true" + // } + // } + // } + + // return licenseDetails, nil + // } + + return licenseDetails, nil +} + func getJwtStruct() (*JWTStruct, error) { accessToken, err := GetAccessToken() if err != nil { diff --git a/internal/wrappers/mock/jwt-helper-mock.go b/internal/wrappers/mock/jwt-helper-mock.go index 1286b4530..1e5f064a8 100644 --- a/internal/wrappers/mock/jwt-helper-mock.go +++ b/internal/wrappers/mock/jwt-helper-mock.go @@ -75,3 +75,18 @@ func (j *JWTMockWrapper) IsAllowedEngine(engine string) (bool, error) { func (j *JWTMockWrapper) CheckPermissionByAccessToken(requiredPermission string) (permission bool, err error) { return true, nil } + +// GetLicenseDetails mock for tests +func (j *JWTMockWrapper) GetLicenseDetails(featureFlagsWrapper wrappers.FeatureFlagsWrapper) (licenseDetails map[string]string, err error) { + licenseDetails = make(map[string]string) + + // Add scan.config.plugins.standalone as true + licenseDetails["scan.config.plugins.standalone"] = "true" + + // Add mock engine license data + for _, engine := range engines { + licenseDetails[engine] = "true" + } + + return licenseDetails, nil +} From d0ec2f8361aa133ffdfe28f9268d1541eb2739b3 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:27:46 +0530 Subject: [PATCH 02/16] removed commented code --- internal/wrappers/jwt-helper.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/internal/wrappers/jwt-helper.go b/internal/wrappers/jwt-helper.go index b42561804..d59f8e43e 100644 --- a/internal/wrappers/jwt-helper.go +++ b/internal/wrappers/jwt-helper.go @@ -79,31 +79,11 @@ func (*JWTStruct) GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (al // GetLicenseDetails will return all license information from AstLicense.LicenseData.AllowedEngines func (*JWTStruct) GetLicenseDetails(featureFlagsWrapper FeatureFlagsWrapper) (licenseDetails map[string]string, err error) { - //flagResponse, _ := GetSpecificFeatureFlag(featureFlagsWrapper, PackageEnforcementEnabled) licenseDetails = make(map[string]string) // Add scan.config.plugins.standalone as true licenseDetails["scan.config.plugins.standalone"] = "true" - - // if flagResponse.Status { - // jwtStruct, err := getJwtStruct() - // if err != nil { - // return licenseDetails, nil // Return what we have so far if JWT validation fails - // } - - // // Return all engines from license data as true (they are allowed engines) - // if jwtStruct.AstLicense.LicenseData.AllowedEngines != nil { - // for _, engine := range jwtStruct.AstLicense.LicenseData.AllowedEngines { - // if engine != "" { - // licenseDetails[engine] = "true" - // } - // } - // } - - // return licenseDetails, nil - // } - return licenseDetails, nil } From e15e699c9e7cc815b46e7405920d078b2f0fb1c6 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:45:24 +0530 Subject: [PATCH 03/16] removed commented code --- internal/wrappers/jwt-helper.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/wrappers/jwt-helper.go b/internal/wrappers/jwt-helper.go index d59f8e43e..8ff0a4222 100644 --- a/internal/wrappers/jwt-helper.go +++ b/internal/wrappers/jwt-helper.go @@ -79,7 +79,6 @@ func (*JWTStruct) GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (al // GetLicenseDetails will return all license information from AstLicense.LicenseData.AllowedEngines func (*JWTStruct) GetLicenseDetails(featureFlagsWrapper FeatureFlagsWrapper) (licenseDetails map[string]string, err error) { - licenseDetails = make(map[string]string) // Add scan.config.plugins.standalone as true From 9435b252b11bf4e50e298c1a219d39655e373fab Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:46:09 +0530 Subject: [PATCH 04/16] updated test cases and added license details in respose --- internal/commands/util/tenant.go | 1 - internal/params/flags.go | 1 + internal/wrappers/jwt-helper.go | 22 +++++++++++++++++++--- internal/wrappers/mock/jwt-helper-mock.go | 10 ++++++---- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/internal/commands/util/tenant.go b/internal/commands/util/tenant.go index 62b9df641..0b7447630 100644 --- a/internal/commands/util/tenant.go +++ b/internal/commands/util/tenant.go @@ -53,7 +53,6 @@ func runTenantCmd(wrapper wrappers.TenantConfigurationWrapper, jwtWrapper wrappe format, _ := cmd.Flags().GetString(params.FormatFlag) tenantConfigurationResponseView := toTenantConfigurationResponseView(tenantConfigurationResponse) - // Get license details and add them to the response licenseDetails, err := jwtWrapper.GetLicenseDetails(featureFlagsWrapper) if err == nil { tenantConfigurationResponseView = appendLicenseDetails(tenantConfigurationResponseView, licenseDetails) diff --git a/internal/params/flags.go b/internal/params/flags.go index 2eb507d52..e29f3c2db 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -289,6 +289,7 @@ const ( APISecurityType = "api-security" AIProtectionType = "AI Protection" CheckmarxOneAssistType = "Checkmarx One Assist" + CheckmarxOneStandAloneType = "Standalone" ContainersType = "containers" APIDocumentationFlag = "apisec-swagger-filter" IacType = "iac-security" diff --git a/internal/wrappers/jwt-helper.go b/internal/wrappers/jwt-helper.go index 8ff0a4222..7d7ab1669 100644 --- a/internal/wrappers/jwt-helper.go +++ b/internal/wrappers/jwt-helper.go @@ -1,6 +1,7 @@ package wrappers import ( + "strconv" "strings" commonParams "github.com/checkmarx/ast-cli/internal/params" @@ -77,12 +78,27 @@ func (*JWTStruct) GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (al return getDefaultEngines(scsLicensingV2Flag.Status), nil } -// GetLicenseDetails will return all license information from AstLicense.LicenseData.AllowedEngines func (*JWTStruct) GetLicenseDetails(featureFlagsWrapper FeatureFlagsWrapper) (licenseDetails map[string]string, err error) { licenseDetails = make(map[string]string) - // Add scan.config.plugins.standalone as true - licenseDetails["scan.config.plugins.standalone"] = "true" + jwtStruct, err := getJwtStruct() + if err != nil { + return nil, err + } + + assistEnabled := false + standaloneEnabled := false + for _, allowedEngine := range jwtStruct.AstLicense.LicenseData.AllowedEngines { + if strings.EqualFold(allowedEngine, commonParams.CheckmarxOneAssistType) || + strings.EqualFold(allowedEngine, commonParams.AIProtectionType) { + assistEnabled = true + } else if strings.EqualFold(allowedEngine, commonParams.CheckmarxOneStandAloneType) { + standaloneEnabled = true + } + } + + licenseDetails["scan.config.plugins.cxoneassist"] = strconv.FormatBool(assistEnabled) + licenseDetails["scan.config.plugins.standalone"] = strconv.FormatBool(standaloneEnabled) return licenseDetails, nil } diff --git a/internal/wrappers/mock/jwt-helper-mock.go b/internal/wrappers/mock/jwt-helper-mock.go index 1e5f064a8..b71ef983e 100644 --- a/internal/wrappers/mock/jwt-helper-mock.go +++ b/internal/wrappers/mock/jwt-helper-mock.go @@ -1,6 +1,7 @@ package mock import ( + "strconv" "strings" "github.com/checkmarx/ast-cli/internal/params" @@ -76,14 +77,15 @@ func (j *JWTMockWrapper) CheckPermissionByAccessToken(requiredPermission string) return true, nil } -// GetLicenseDetails mock for tests func (j *JWTMockWrapper) GetLicenseDetails(featureFlagsWrapper wrappers.FeatureFlagsWrapper) (licenseDetails map[string]string, err error) { licenseDetails = make(map[string]string) - // Add scan.config.plugins.standalone as true - licenseDetails["scan.config.plugins.standalone"] = "true" + assistEnabled := (j.CheckmarxOneAssistEnabled != CheckmarxOneAssistDisabled) || (j.AIEnabled != AIProtectionDisabled) + licenseDetails["scan.config.plugins.cxoneassist"] = strconv.FormatBool(assistEnabled) + + standaloneEnabled := true + licenseDetails["scan.config.plugins.standalone"] = strconv.FormatBool(standaloneEnabled) - // Add mock engine license data for _, engine := range engines { licenseDetails[engine] = "true" } From 7d905f8190a6488ae945d881ed07b6dfa4dbc407 Mon Sep 17 00:00:00 2001 From: Hitesh Madgulkar <212497904+cx-hitesh-madgulkar@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:38:19 +0530 Subject: [PATCH 05/16] changed-file-path-to-download-vorpal-sca-resolver (#1344) --- .../osinstaller/os-installer-structs.go | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/internal/services/osinstaller/os-installer-structs.go b/internal/services/osinstaller/os-installer-structs.go index 69397bde9..12f61cc52 100644 --- a/internal/services/osinstaller/os-installer-structs.go +++ b/internal/services/osinstaller/os-installer-structs.go @@ -15,13 +15,28 @@ type InstallationConfiguration struct { } func (i *InstallationConfiguration) ExecutableFilePath() string { - return filepath.Join(os.TempDir(), i.WorkingDirName, i.ExecutableFile) + basePath := os.TempDir() + homeDir, err := os.UserHomeDir() + if err == nil { + basePath = homeDir + "/.checkmarx/" + } + return filepath.Join(basePath, i.WorkingDirName, i.ExecutableFile) } func (i *InstallationConfiguration) HashFilePath() string { - return filepath.Join(os.TempDir(), i.WorkingDirName, i.HashFileName) + basePath := os.TempDir() + homeDir, err := os.UserHomeDir() + if err == nil { + basePath = homeDir + "/.checkmarx/" + } + return filepath.Join(basePath, i.WorkingDirName, i.HashFileName) } func (i *InstallationConfiguration) WorkingDir() string { - return filepath.Join(os.TempDir(), i.WorkingDirName) + basePath := os.TempDir() + homeDir, err := os.UserHomeDir() + if err == nil { + basePath = homeDir + "/.checkmarx/" + } + return filepath.Join(basePath, i.WorkingDirName) } From c899597330f8eef9aa21054babbbe4ea9e660168 Mon Sep 17 00:00:00 2001 From: Anurag Dalke Date: Wed, 5 Nov 2025 20:45:47 +0530 Subject: [PATCH 06/16] Updated macos version (AST-0000) (#1346) * updated GH action * updated macos gh action version --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e6fe9df9..eb7b8bb1f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ permissions: jobs: build: - runs-on: macos-13 + runs-on: macos-latest env: AC_PASSWORD: ${{ secrets.AC_PASSWORD }} APPLE_DEVELOPER_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} @@ -84,7 +84,7 @@ jobs: - name: Install Cosign if: inputs.dev == false run: | - brew install sigstore/tap/cosign + brew install cosign - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 #v2 From de695778adf707e051d7c4bc257862c952473db2 Mon Sep 17 00:00:00 2001 From: Anurag Dalke Date: Wed, 5 Nov 2025 20:58:41 +0530 Subject: [PATCH 07/16] Change macOS runner and add architecture detection Updated macOS runner version and added architecture detection step. --- .github/workflows/release.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb7b8bb1f..c44b6e818 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ permissions: jobs: build: - runs-on: macos-latest + runs-on: macos-13 env: AC_PASSWORD: ${{ secrets.AC_PASSWORD }} APPLE_DEVELOPER_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} @@ -66,6 +66,11 @@ jobs: - name: Install gon run: | brew install Bearer/tap/gon + # New: detect architecture and expose as step output + - name: Detect runner architecture + id: arch + run: | + echo "arch=$(uname -m)" >> $GITHUB_OUTPUT - name: Setup Docker on macOS if: inputs.dev == false uses: douglascamata/setup-docker-macos-action@v1.0.1 From 8128997a008924919b6b6dbb2dcb0d80eb539d4b Mon Sep 17 00:00:00 2001 From: Anurag Dalke Date: Wed, 5 Nov 2025 21:57:29 +0530 Subject: [PATCH 08/16] Modify Cosign installation in release workflow Updated Cosign installation command to use the tap and added postinstall for OpenSSL. --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c44b6e818..c9d45c8e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -89,7 +89,8 @@ jobs: - name: Install Cosign if: inputs.dev == false run: | - brew install cosign + brew install sigstore/tap/cosign + brew postinstall openssl@3 || true - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 #v2 From 4b874b75310b6410d77d27aa58e0976be9399d07 Mon Sep 17 00:00:00 2001 From: Anurag Dalke Date: Wed, 5 Nov 2025 22:45:21 +0530 Subject: [PATCH 09/16] Update release.yml --- .github/workflows/release.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9d45c8e9..3f81a83ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,6 @@ jobs: git checkout 4.4.15 export HOMEBREW_NO_AUTO_UPDATE=1 brew --version - - name: Install gon run: | brew install Bearer/tap/gon @@ -88,9 +87,7 @@ jobs: - name: Install Cosign if: inputs.dev == false - run: | - brew install sigstore/tap/cosign - brew postinstall openssl@3 || true + uses: sigstore/cosign-installer@v3.2.0 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 #v2 From 0fc316893d0f6568516ded040fe58e8b7d24dbaf Mon Sep 17 00:00:00 2001 From: cx-Margarita-LevitM Date: Thu, 13 Nov 2025 08:08:09 +0200 Subject: [PATCH 10/16] Fix AST-120637 vulnerability undate ../containerd from v 1.7.28 to 1.7.29 --- go.mod | 10 +++++----- go.sum | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index b42b0e7a4..52cdb7613 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/crypto v0.40.0 @@ -36,6 +36,8 @@ require ( gotest.tools v2.2.0+incompatible ) +require cyphar.com/go-pathrs v0.2.1 // indirect + require ( dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect @@ -100,7 +102,7 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deitch/magic v0.0.0-20240306090643-c67ab88f10cb // indirect github.com/distribution/reference v0.6.0 // indirect @@ -217,7 +219,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect - github.com/opencontainers/selinux v1.12.0 // indirect + github.com/opencontainers/selinux v1.13.0 // indirect github.com/pborman/indent v1.2.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect @@ -320,5 +322,3 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.5.0 // indirect ) - -replace github.com/containerd/containerd => github.com/containerd/containerd v1.7.27 diff --git a/go.sum b/go.sum index 1aa58d080..28bcf70a4 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= +cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -247,8 +249,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= -github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= -github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= +github.com/containerd/containerd v1.7.28 h1:Nsgm1AtcmEh4AHAJ4gGlNSaKgXiNccU270Dnf81FQ3c= +github.com/containerd/containerd v1.7.28/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= @@ -277,8 +279,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -803,8 +805,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= -github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= +github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= +github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -964,8 +966,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= From 05cccbcbbc6383abd177d4807a3bd7c93fb7ac0a Mon Sep 17 00:00:00 2001 From: Margarita Levit <45617576+cx-margarita-levitm@users.noreply.github.com> Date: Mon, 17 Nov 2025 07:34:09 +0200 Subject: [PATCH 11/16] Fix AST-120637 vulnerability undate ../containerd from v 1.7.28 to 1.7.29 (AST-120637) (#1351) * Fix AST-120637 vulnerability * Fix AST-120637 vulnerability undate ../containerd from v 1.7.28 to 1.7.29 * Revert Fix AST-120637 vulnerability undate ../containerd from v 1.7.28 to 1.7.29 push * Revert * Fix Iac vulnerabilities * Fix Iac vulnerabilities * Fix Iac vulnerabilities * skip test cases TestPRAzureDecorationSuccessCase --------- Co-authored-by: cx-Margarita-LevitM Co-authored-by: cx-anurag-dalke <120229307+cx-anurag-dalke@users.noreply.github.com> --- .github/workflows/ci-tests.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- go.mod | 6 +++++- go.sum | 4 ++-- test/integration/pr_test.go | 7 +++---- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 205099bb4..217b51eb4 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -80,8 +80,8 @@ jobs: AZURE_PROJECT: ${{ secrets.AZURE_PROJECT }} AZURE_REPOS: ${{ secrets.AZURE_REPOS }} AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }} - AZURE_NEW_ORG: "azureAccountTests" - AZURE_PROJECT_NAME: "testsProject" + AZURE_NEW_ORG: ${{ secrets.AZURE_NEW_ORG }} + AZURE_PROJECT_NAME: ${{ secrets.AZURE_PROJECT_NAME }} AZURE_PR_NUMBER: 1 AZURE_NEW_TOKEN: ${{ secrets.AZURE_NEW_TOKEN }} BITBUCKET_WORKSPACE: ${{ secrets.BITBUCKET_WORKSPACE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f81a83ab..ab8671dae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: echo "arch=$(uname -m)" >> $GITHUB_OUTPUT - name: Setup Docker on macOS if: inputs.dev == false - uses: douglascamata/setup-docker-macos-action@v1.0.1 + uses: douglascamata/setup-docker-macos-action@de9dc31504356970f7ab9b785d6afee99b994692 #v1.0.1 - name: Test docker if: inputs.dev == false run: | @@ -87,7 +87,7 @@ jobs: - name: Install Cosign if: inputs.dev == false - uses: sigstore/cosign-installer@v3.2.0 + uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 #v3.2.0 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 #v2 diff --git a/go.mod b/go.mod index 52cdb7613..cf5adc258 100644 --- a/go.mod +++ b/go.mod @@ -91,7 +91,7 @@ require ( github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/containerd/cgroups/v3 v3.0.5 // indirect - github.com/containerd/containerd v1.7.28 // indirect + github.com/containerd/containerd v1.7.29 // indirect github.com/containerd/containerd/api v1.9.0 // indirect github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect @@ -322,3 +322,7 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.5.0 // indirect ) + +replace github.com/containerd/containerd => github.com/containerd/containerd v1.7.29 + +replace github.com/opencontainers/selinux => github.com/opencontainers/selinux v1.13.0 diff --git a/go.sum b/go.sum index 28bcf70a4..d21fce87d 100644 --- a/go.sum +++ b/go.sum @@ -249,8 +249,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= -github.com/containerd/containerd v1.7.28 h1:Nsgm1AtcmEh4AHAJ4gGlNSaKgXiNccU270Dnf81FQ3c= -github.com/containerd/containerd v1.7.28/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= +github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= +github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= diff --git a/test/integration/pr_test.go b/test/integration/pr_test.go index 0fa20b0fe..83f14f72d 100644 --- a/test/integration/pr_test.go +++ b/test/integration/pr_test.go @@ -8,13 +8,11 @@ import ( "strings" "testing" - "github.com/checkmarx/ast-cli/internal/wrappers" - + "github.com/bouk/monkey" "github.com/checkmarx/ast-cli/internal/commands/util" "github.com/checkmarx/ast-cli/internal/logger" - - "github.com/bouk/monkey" "github.com/checkmarx/ast-cli/internal/params" + "github.com/checkmarx/ast-cli/internal/wrappers" "gotest.tools/assert" ) @@ -254,6 +252,7 @@ func TestPRGitlabDecorationFailure(t *testing.T) { } func TestPRAzureDecorationSuccessCase(t *testing.T) { + t.Skip("Skipping this test for now") args := []string{ "utils", "pr", From 2d2b7498a894f5c2a08df67193606b1ae381050c Mon Sep 17 00:00:00 2001 From: Dmitri Rivin <90623914+cx-dmitri-rivin@users.noreply.github.com> Date: Mon, 17 Nov 2025 10:25:21 +0200 Subject: [PATCH 12/16] Containers cloud scan integration tests (AST-107639) (#1347) * integration tets done * tests fix --------- Co-authored-by: Anurag Dalke --- .../container_empty_folder_test.go | 131 +++++++ .../container_images_validation_test.go | 281 ++++++++++++++ .../container_scan_edge_cases_test.go | 342 ++++++++++++++++++ 3 files changed, 754 insertions(+) create mode 100644 test/integration/container_empty_folder_test.go create mode 100644 test/integration/container_images_validation_test.go create mode 100644 test/integration/container_scan_edge_cases_test.go diff --git a/test/integration/container_empty_folder_test.go b/test/integration/container_empty_folder_test.go new file mode 100644 index 000000000..07334373d --- /dev/null +++ b/test/integration/container_empty_folder_test.go @@ -0,0 +1,131 @@ +//go:build integration + +package integration + +import ( + "testing" + + "github.com/checkmarx/ast-cli/internal/commands/util/printer" + "github.com/checkmarx/ast-cli/internal/params" + "gotest.tools/assert" +) + +// TestContainerScan_EmptyFolderWithExternalImages tests scanning with empty folders and external container images +func TestContainerScan_EmptyFolderWithExternalImages(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/empty-folder.zip", + flag(params.ContainerImagesFlag), "nginx:alpine", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for empty folder with external images") + assert.Assert(t, projectID != "", "Project ID should not be empty for empty folder with external images") +} + +// TestContainerScan_EmptyFolderWithMultipleExternalImages tests scanning empty folder with multiple external images +func TestContainerScan_EmptyFolderWithMultipleExternalImages(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/empty-folder.zip", + flag(params.ContainerImagesFlag), "nginx:alpine,mysql:5.7,debian:9", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for empty folder with multiple external images") + assert.Assert(t, projectID != "", "Project ID should not be empty for empty folder with multiple external images") +} + +// TestContainerScan_EmptyFolderWithExternalImagesAndDebug tests with debug flag enabled +func TestContainerScan_EmptyFolderWithExternalImagesAndDebug(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/empty-folder.zip", + flag(params.ContainerImagesFlag), "mysql:5.7", + flag(params.BranchFlag), "dummy_branch", + flag(params.DebugFlag), + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for empty folder with debug flag") + assert.Assert(t, projectID != "", "Project ID should not be empty for empty folder with debug flag") +} + +// TestContainerScan_EmptyFolderWithComplexImageNames tests empty folder with complex image names +func TestContainerScan_EmptyFolderWithComplexImageNames(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/empty-folder.zip", + flag(params.ContainerImagesFlag), "docker.io/library/nginx:1.21.6,mysql:5.7.38", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for complex image names") + assert.Assert(t, projectID != "", "Project ID should not be empty for complex image names") +} + +// TestContainerScan_EmptyFolderWithRegistryImages tests empty folder with registry-prefixed images +func TestContainerScan_EmptyFolderWithRegistryImages(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/empty-folder.zip", + flag(params.ContainerImagesFlag), "checkmarx/kics:v2.1.11", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for registry images") + assert.Assert(t, projectID != "", "Project ID should not be empty for registry images") +} + +// TestContainerScan_EmptyFolderInvalidImageShouldFail tests that validation still works with empty folders +func TestContainerScan_EmptyFolderInvalidImageShouldFail(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/empty-folder.zip", + flag(params.ContainerImagesFlag), "invalid-image-without-tag", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for invalid image format with empty folder") + assertError(t, err, "image does not have a tag") +} + +// TestContainerScan_EmptyFolderMixedValidInvalidImages tests mixed valid/invalid images with empty folder +func TestContainerScan_EmptyFolderMixedValidInvalidImages(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/empty-folder.zip", + flag(params.ContainerImagesFlag), "nginx:alpine,invalid-image,mysql:5.7", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for mixed valid/invalid images with empty folder") + assertError(t, err, "image does not have a tag") +} diff --git a/test/integration/container_images_validation_test.go b/test/integration/container_images_validation_test.go new file mode 100644 index 000000000..217ca29f2 --- /dev/null +++ b/test/integration/container_images_validation_test.go @@ -0,0 +1,281 @@ +//go:build integration + +package integration + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/checkmarx/ast-cli/internal/commands/util/printer" + "github.com/checkmarx/ast-cli/internal/params" + "gotest.tools/assert" +) + +// TestContainerImageValidation_ValidFormats tests that valid container image formats are accepted +func TestContainerImageValidation_ValidFormats(t *testing.T) { + tests := []struct { + name string + imageFormat string + description string + }{ + { + name: "SimpleImageWithTag", + imageFormat: "nginx:alpine", + description: "Simple image with tag should be valid", + }, + { + name: "ImageWithRegistryAndTag", + imageFormat: "docker.io/nginx:1.21", + description: "Image with registry and tag should be valid", + }, + { + name: "ImageWithPortAndTag", + imageFormat: "localhost:5000/myapp:latest", + description: "Image with port and tag should be valid", + }, + { + name: "ImageWithNamespaceAndTag", + imageFormat: "checkmarx/kics:v2.1.11", + description: "Image with namespace and tag should be valid", + }, + { + name: "FullyQualifiedImageWithTag", + imageFormat: "registry.example.com:8080/namespace/myapp:v1.0.0", + description: "Fully qualified image with tag should be valid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), tt.imageFormat, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for: "+tt.description) + assert.Assert(t, projectID != "", "Project ID should not be empty for: "+tt.description) + }) + } +} + +// TestContainerImageValidation_InvalidFormats tests that invalid container image formats are rejected +func TestContainerImageValidation_InvalidFormats(t *testing.T) { + tests := []struct { + name string + imageFormat string + description string + expectedError string + }{ + { + name: "ImageWithoutTag", + imageFormat: "nginx", + description: "Image without tag should be invalid", + expectedError: "image does not have a tag", + }, + { + name: "ImageWithEmptyTag", + imageFormat: "nginx:", + description: "Image with empty tag should be invalid", + expectedError: "Image name and tag cannot be empty", + }, + { + name: "ImageWithEmptyName", + imageFormat: ":alpine", + description: "Image with empty name should be invalid", + expectedError: "Image name and tag cannot be empty", + }, + { + name: "OnlyColon", + imageFormat: ":", + description: "Only colon should be invalid", + expectedError: "Image name and tag cannot be empty", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), tt.imageFormat, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for: "+tt.description) + if tt.expectedError != "" { + assertError(t, err, tt.expectedError) + } + }) + } +} + +// TestContainerImageValidation_MultipleImagesValidation tests validation with multiple container images +func TestContainerImageValidation_MultipleImagesValidation(t *testing.T) { + tests := []struct { + name string + imageList string + shouldSucceed bool + description string + }{ + { + name: "MultipleValidImages", + imageList: "nginx:alpine,mysql:5.7,debian:9", + shouldSucceed: true, + description: "Multiple valid images should pass", + }, + { + name: "OneInvalidAmongValid", + imageList: "nginx:alpine,invalid-image,mysql:5.7", + shouldSucceed: false, + description: "One invalid image among valid ones should fail", + }, + { + name: "LastImageInvalid", + imageList: "nginx:alpine,mysql:5.7,invalid:", + shouldSucceed: false, + description: "Last image being invalid should fail", + }, + { + name: "FirstImageInvalid", + imageList: ":invalid,nginx:alpine,mysql:5.7", + shouldSucceed: false, + description: "First image being invalid should fail", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), tt.imageList, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + + if tt.shouldSucceed { + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for: "+tt.description) + assert.Assert(t, projectID != "", "Project ID should not be empty for: "+tt.description) + } else { + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for: "+tt.description) + } + }) + } +} + +// TestContainerImageValidation_TarFiles tests validation of .tar file references +func TestContainerImageValidation_TarFiles(t *testing.T) { + // Create a temporary .tar file for testing + tempDir := t.TempDir() + validTarFile := filepath.Join(tempDir, "test-image.tar") + + // Create an empty .tar file for testing + f, err := os.Create(validTarFile) + assert.NilError(t, err, "Should create temp .tar file") + f.Close() + + tests := []struct { + name string + tarFile string + shouldSucceed bool + description string + }{ + { + name: "ValidTarFile", + tarFile: validTarFile, + shouldSucceed: true, + description: "Valid .tar file path should be accepted", + }, + { + name: "NonExistentTarFile", + tarFile: "/tmp/nonexistent-image.tar", + shouldSucceed: false, + description: "Non-existent .tar file should be rejected", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), tt.tarFile, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + + if tt.shouldSucceed { + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for: "+tt.description) + assert.Assert(t, projectID != "", "Project ID should not be empty for: "+tt.description) + } else { + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for: "+tt.description) + } + }) + } +} + +// TestContainerImageValidation_MixedTarAndRegularImages tests mixing .tar files with regular images +func TestContainerImageValidation_MixedTarAndRegularImages(t *testing.T) { + // Create a temporary .tar file for testing + tempDir := t.TempDir() + validTarFile := filepath.Join(tempDir, "test-image.tar") + + f, err := os.Create(validTarFile) + assert.NilError(t, err, "Should create temp .tar file") + f.Close() + + t.Run("ValidTarAndRegularImage", func(t *testing.T) { + createASTIntegrationTestCommand(t) + imageList := fmt.Sprintf("nginx:alpine,%s", validTarFile) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), imageList, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for mixed valid images") + assert.Assert(t, projectID != "", "Project ID should not be empty for mixed valid images") + }) + + t.Run("ValidTarAndInvalidRegularImage", func(t *testing.T) { + createASTIntegrationTestCommand(t) + imageList := fmt.Sprintf("nginx:,%s", validTarFile) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), imageList, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for mixed images with one invalid") + }) +} diff --git a/test/integration/container_scan_edge_cases_test.go b/test/integration/container_scan_edge_cases_test.go new file mode 100644 index 000000000..4d5054703 --- /dev/null +++ b/test/integration/container_scan_edge_cases_test.go @@ -0,0 +1,342 @@ +//go:build integration + +package integration + +import ( + "os" + "path/filepath" + "testing" + + "github.com/checkmarx/ast-cli/internal/commands/util/printer" + "github.com/checkmarx/ast-cli/internal/params" + "gotest.tools/assert" +) + +// TestContainerScan_ErrorHandling_InvalidAndValidScenarios tests error handling improvements +func TestContainerScan_ErrorHandling_InvalidAndValidScenarios(t *testing.T) { + t.Run("ValidScanWithAllParameters", func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/Dockerfile-mysql571.zip", + flag(params.ContainerImagesFlag), "nginx:alpine,mysql:5.7", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty") + assert.Assert(t, projectID != "", "Project ID should not be empty") + }) + + t.Run("InvalidImageInList", func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/Dockerfile-mysql571.zip", + flag(params.ContainerImagesFlag), "nginx:alpine,bad-image,mysql:5.7", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + } + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for invalid image in list") + }) +} + +// TestContainerScan_TarFileValidation tests .tar file validation scenarios +func TestContainerScan_TarFileValidation(t *testing.T) { + tempDir := t.TempDir() + + t.Run("ExistingTarFile", func(t *testing.T) { + // Create a dummy .tar file + tarFile := filepath.Join(tempDir, "test-container.tar") + f, err := os.Create(tarFile) + assert.NilError(t, err) + f.Close() + + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), tarFile, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for existing tar file") + assert.Assert(t, projectID != "", "Project ID should not be empty for existing tar file") + }) + + t.Run("NonExistentTarFile", func(t *testing.T) { + nonExistentTar := filepath.Join(tempDir, "nonexistent.tar") + + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), nonExistentTar, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + } + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for non-existent tar file") + }) + + t.Run("TarFileWithOtherImages", func(t *testing.T) { + // Create a dummy .tar file + tarFile := filepath.Join(tempDir, "another-test.tar") + f, err := os.Create(tarFile) + assert.NilError(t, err) + f.Close() + + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), "nginx:alpine," + tarFile, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for tar file with other images") + assert.Assert(t, projectID != "", "Project ID should not be empty for tar file with other images") + }) +} + +// TestContainerScan_SpecialCharactersInImageNames tests handling of special characters +func TestContainerScan_SpecialCharactersInImageNames(t *testing.T) { + tests := []struct { + name string + imageName string + shouldSucceed bool + description string + }{ + { + name: "ImageWithHyphen", + imageName: "my-image:latest", + shouldSucceed: true, + description: "Image with hyphen should be valid", + }, + { + name: "ImageWithUnderscore", + imageName: "my_image:v1.0", + shouldSucceed: true, + description: "Image with underscore should be valid", + }, + { + name: "ImageWithDots", + imageName: "my.image.name:1.2.3", + shouldSucceed: true, + description: "Image with dots should be valid", + }, + { + name: "ImageWithSlash", + imageName: "namespace/image:tag", + shouldSucceed: true, + description: "Image with namespace slash should be valid", + }, + { + name: "ComplexRegistry", + imageName: "registry.example.com:5000/namespace/image:v1.0.0", + shouldSucceed: true, + description: "Complex registry path should be valid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), tt.imageName, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + + if tt.shouldSucceed { + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for: "+tt.description) + assert.Assert(t, projectID != "", "Project ID should not be empty for: "+tt.description) + } else { + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for: "+tt.description) + } + }) + } +} + +// TestContainerScan_ImageTagVariations tests different tag formats +func TestContainerScan_ImageTagVariations(t *testing.T) { + tests := []struct { + name string + imageName string + shouldSucceed bool + description string + }{ + { + name: "LatestTag", + imageName: "nginx:latest", + shouldSucceed: true, + description: "Latest tag should be valid", + }, + { + name: "NumericTag", + imageName: "nginx:1.21", + shouldSucceed: true, + description: "Numeric tag should be valid", + }, + { + name: "SemanticVersionTag", + imageName: "nginx:1.21.6", + shouldSucceed: true, + description: "Semantic version tag should be valid", + }, + { + name: "AlphanumericTag", + imageName: "nginx:v2.1.11", + shouldSucceed: true, + description: "Alphanumeric tag with prefix should be valid", + }, + { + name: "TagWithHyphen", + imageName: "nginx:stable-alpine", + shouldSucceed: true, + description: "Tag with hyphen should be valid", + }, + { + name: "SHA256Tag", + imageName: "nginx:sha256", + shouldSucceed: true, + description: "SHA256 tag should be valid", + }, + { + name: "TagWithUnderscore", + imageName: "nginx:stable_release", + shouldSucceed: true, + description: "Tag with underscore should be valid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), tt.imageName, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + + if tt.shouldSucceed { + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for: "+tt.description) + assert.Assert(t, projectID != "", "Project ID should not be empty for: "+tt.description) + } else { + err, _ := executeCommand(t, testArgs...) + assert.Assert(t, err != nil, "Expected error for: "+tt.description) + } + }) + } +} + +// TestContainerScan_BoundaryConditions tests boundary conditions +func TestContainerScan_BoundaryConditions(t *testing.T) { + t.Run("SingleCharacterImageName", func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), "a:b", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for single character image") + assert.Assert(t, projectID != "", "Project ID should not be empty for single character image") + }) + + t.Run("VeryLongImageName", func(t *testing.T) { + longName := "verylongimagenamethatshouldstillbevalidaslongasithasapropertagformat:v1.0.0" + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), longName, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for long image name") + assert.Assert(t, projectID != "", "Project ID should not be empty for long image name") + }) + + t.Run("ManyImagesInList", func(t *testing.T) { + // Test with 5 images (reduced from 10 to avoid timeout) + images := "nginx:1,nginx:2,nginx:3,nginx:4,nginx:5" + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), images, + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for many images") + assert.Assert(t, projectID != "", "Project ID should not be empty for many images") + }) +} + +// TestContainerScan_CombinedWithOtherScanTypes tests container scans combined with other scan types +func TestContainerScan_CombinedWithOtherScanTypes(t *testing.T) { + t.Run("ContainerAndIaC", func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/iac-insecure.zip", + flag(params.ContainerImagesFlag), "nginx:alpine", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag + "," + params.IacType, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for combined scan") + assert.Assert(t, projectID != "", "Project ID should not be empty for combined scan") + }) + + t.Run("ContainerWithSCA", func(t *testing.T) { + createASTIntegrationTestCommand(t) + testArgs := []string{ + "scan", "create", + flag(params.ProjectName), getProjectNameForScanTests(), + flag(params.SourcesFlag), "data/insecure.zip", + flag(params.ContainerImagesFlag), "nginx:alpine", + flag(params.BranchFlag), "dummy_branch", + flag(params.ScanTypes), params.ContainersTypeFlag + "," + params.ScaType, + flag(params.ScanInfoFormatFlag), printer.FormatJSON, + } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty for container+sca scan") + assert.Assert(t, projectID != "", "Project ID should not be empty for container+sca scan") + }) +} From c635469a10d99adb815933de80694d2a2d8f8583 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:04:37 +0530 Subject: [PATCH 13/16] fixed lint issue --- internal/wrappers/mock/jwt-helper-mock.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/wrappers/mock/jwt-helper-mock.go b/internal/wrappers/mock/jwt-helper-mock.go index b71ef983e..9886e9172 100644 --- a/internal/wrappers/mock/jwt-helper-mock.go +++ b/internal/wrappers/mock/jwt-helper-mock.go @@ -23,6 +23,8 @@ const SecretDetectionDisabled = 1 var engines = []string{"sast", "sca", "api-security", "iac-security", "scs", "containers", "enterprise-secrets"} +const licenseEnabledValue = "true" + // GetAllowedEngines mock for tests func (j *JWTMockWrapper) GetAllowedEngines(featureFlagsWrapper wrappers.FeatureFlagsWrapper) (allowedEngines map[string]bool, err error) { if j.CustomGetAllowedEngines != nil { @@ -87,7 +89,7 @@ func (j *JWTMockWrapper) GetLicenseDetails(featureFlagsWrapper wrappers.FeatureF licenseDetails["scan.config.plugins.standalone"] = strconv.FormatBool(standaloneEnabled) for _, engine := range engines { - licenseDetails[engine] = "true" + licenseDetails[engine] = licenseEnabledValue } return licenseDetails, nil From 14b648b2c9f29c12fbe6532f3586d58d2fc20ebf Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Mon, 17 Nov 2025 19:57:31 +0530 Subject: [PATCH 14/16] removed unused parameter --- internal/commands/util/tenant.go | 8 ++++---- internal/commands/util/tenant_test.go | 12 ++++++------ internal/commands/util/utils.go | 2 +- internal/wrappers/jwt-helper.go | 4 ++-- internal/wrappers/mock/jwt-helper-mock.go | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/commands/util/tenant.go b/internal/commands/util/tenant.go index 0b7447630..07b3ac16c 100644 --- a/internal/commands/util/tenant.go +++ b/internal/commands/util/tenant.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -func NewTenantConfigurationCommand(wrapper wrappers.TenantConfigurationWrapper, jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) *cobra.Command { +func NewTenantConfigurationCommand(wrapper wrappers.TenantConfigurationWrapper, jwtWrapper wrappers.JWTWrapper) *cobra.Command { cmd := &cobra.Command{ Use: "tenant", Short: "Shows the tenant settings", @@ -27,7 +27,7 @@ func NewTenantConfigurationCommand(wrapper wrappers.TenantConfigurationWrapper, `, ), }, - RunE: runTenantCmd(wrapper, jwtWrapper, featureFlagsWrapper), + RunE: runTenantCmd(wrapper, jwtWrapper), } cmd.PersistentFlags().String( params.FormatFlag, @@ -40,7 +40,7 @@ func NewTenantConfigurationCommand(wrapper wrappers.TenantConfigurationWrapper, return cmd } -func runTenantCmd(wrapper wrappers.TenantConfigurationWrapper, jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) func(cmd *cobra.Command, args []string) error { +func runTenantCmd(wrapper wrappers.TenantConfigurationWrapper, jwtWrapper wrappers.JWTWrapper) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { tenantConfigurationResponse, errorModel, err := wrapper.GetTenantConfiguration() if err != nil { @@ -53,7 +53,7 @@ func runTenantCmd(wrapper wrappers.TenantConfigurationWrapper, jwtWrapper wrappe format, _ := cmd.Flags().GetString(params.FormatFlag) tenantConfigurationResponseView := toTenantConfigurationResponseView(tenantConfigurationResponse) - licenseDetails, err := jwtWrapper.GetLicenseDetails(featureFlagsWrapper) + licenseDetails, err := jwtWrapper.GetLicenseDetails() if err == nil { tenantConfigurationResponseView = appendLicenseDetails(tenantConfigurationResponseView, licenseDetails) } diff --git a/internal/commands/util/tenant_test.go b/internal/commands/util/tenant_test.go index 67b1a7271..1afac4eb3 100644 --- a/internal/commands/util/tenant_test.go +++ b/internal/commands/util/tenant_test.go @@ -8,41 +8,41 @@ import ( ) func TestTenantConfigurationHelp(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--help"}) err := cmd.Execute() assert.Assert(t, err == nil) } func TestTenantConfigurationJsonFormat(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--format", "json"}) err := cmd.Execute() assert.NilError(t, err, "Tenant configuration command should run with no errors and print to json") } func TestTenantConfigurationListFormat(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--format", "list"}) err := cmd.Execute() assert.NilError(t, err, "Tenant configuration command should run with no errors and print to list") } func TestTenantConfigurationTableFormat(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--format", "table"}) err := cmd.Execute() assert.NilError(t, err, "Tenant configuration command should run with no errors and print to table") } func TestTenantConfigurationInvalidFormat(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}) cmd.SetArgs([]string{"utils", "tenant", "--format", "MOCK"}) err := cmd.Execute() assert.Assert(t, err.Error() == mockFormatErrorMessage) } func TestNewTenantConfigurationCommand(t *testing.T) { - cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) + cmd := NewTenantConfigurationCommand(mock.TenantConfigurationMockWrapper{}, &mock.JWTMockWrapper{}) assert.Assert(t, cmd != nil, "Tenant configuration command must exist") } diff --git a/internal/commands/util/utils.go b/internal/commands/util/utils.go index 3b7285aa8..c16db6f93 100644 --- a/internal/commands/util/utils.go +++ b/internal/commands/util/utils.go @@ -77,7 +77,7 @@ func NewUtilsCommand( learnMoreCmd := NewLearnMoreCommand(learnMoreWrapper) - tenantCmd := NewTenantConfigurationCommand(tenantWrapper, jwtWrapper, featureFlagsWrapper) + tenantCmd := NewTenantConfigurationCommand(tenantWrapper, jwtWrapper) maskSecretsCmd := NewMaskSecretsCommand(chatWrapper) diff --git a/internal/wrappers/jwt-helper.go b/internal/wrappers/jwt-helper.go index 7d7ab1669..0becac8cf 100644 --- a/internal/wrappers/jwt-helper.go +++ b/internal/wrappers/jwt-helper.go @@ -24,7 +24,7 @@ type JWTStruct struct { type JWTWrapper interface { GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (allowedEngines map[string]bool, err error) - GetLicenseDetails(featureFlagsWrapper FeatureFlagsWrapper) (licenseDetails map[string]string, err error) + GetLicenseDetails() (licenseDetails map[string]string, err error) IsAllowedEngine(engine string) (bool, error) ExtractTenantFromToken() (tenant string, err error) CheckPermissionByAccessToken(requiredPermission string) (permission bool, err error) @@ -78,7 +78,7 @@ func (*JWTStruct) GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (al return getDefaultEngines(scsLicensingV2Flag.Status), nil } -func (*JWTStruct) GetLicenseDetails(featureFlagsWrapper FeatureFlagsWrapper) (licenseDetails map[string]string, err error) { +func (*JWTStruct) GetLicenseDetails() (licenseDetails map[string]string, err error) { licenseDetails = make(map[string]string) jwtStruct, err := getJwtStruct() diff --git a/internal/wrappers/mock/jwt-helper-mock.go b/internal/wrappers/mock/jwt-helper-mock.go index 9886e9172..61f64ca91 100644 --- a/internal/wrappers/mock/jwt-helper-mock.go +++ b/internal/wrappers/mock/jwt-helper-mock.go @@ -79,7 +79,7 @@ func (j *JWTMockWrapper) CheckPermissionByAccessToken(requiredPermission string) return true, nil } -func (j *JWTMockWrapper) GetLicenseDetails(featureFlagsWrapper wrappers.FeatureFlagsWrapper) (licenseDetails map[string]string, err error) { +func (j *JWTMockWrapper) GetLicenseDetails() (licenseDetails map[string]string, err error) { licenseDetails = make(map[string]string) assistEnabled := (j.CheckmarxOneAssistEnabled != CheckmarxOneAssistDisabled) || (j.AIEnabled != AIProtectionDisabled) From f41fc36cf31c6d03c49b45e420e0c5987050d65d Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:09:13 +0530 Subject: [PATCH 15/16] updated license as Checkmarx Developer Assist --- internal/params/flags.go | 2 +- internal/wrappers/jwt-helper.go | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/internal/params/flags.go b/internal/params/flags.go index e29f3c2db..805da9414 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -289,7 +289,7 @@ const ( APISecurityType = "api-security" AIProtectionType = "AI Protection" CheckmarxOneAssistType = "Checkmarx One Assist" - CheckmarxOneStandAloneType = "Standalone" + CheckmarxOneStandAloneType = "Checkmarx Developer Assist" ContainersType = "containers" APIDocumentationFlag = "apisec-swagger-filter" IacType = "iac-security" diff --git a/internal/wrappers/jwt-helper.go b/internal/wrappers/jwt-helper.go index 0becac8cf..4ad54c8fc 100644 --- a/internal/wrappers/jwt-helper.go +++ b/internal/wrappers/jwt-helper.go @@ -86,22 +86,25 @@ func (*JWTStruct) GetLicenseDetails() (licenseDetails map[string]string, err err return nil, err } - assistEnabled := false - standaloneEnabled := false - for _, allowedEngine := range jwtStruct.AstLicense.LicenseData.AllowedEngines { - if strings.EqualFold(allowedEngine, commonParams.CheckmarxOneAssistType) || - strings.EqualFold(allowedEngine, commonParams.AIProtectionType) { - assistEnabled = true - } else if strings.EqualFold(allowedEngine, commonParams.CheckmarxOneStandAloneType) { - standaloneEnabled = true - } - } + assistEnabled := containsIgnoreCase(jwtStruct.AstLicense.LicenseData.AllowedEngines, commonParams.CheckmarxOneAssistType) || + containsIgnoreCase(jwtStruct.AstLicense.LicenseData.AllowedEngines, commonParams.AIProtectionType) + devAssistEnabled := containsIgnoreCase(jwtStruct.AstLicense.LicenseData.AllowedEngines, commonParams.CheckmarxOneStandAloneType) licenseDetails["scan.config.plugins.cxoneassist"] = strconv.FormatBool(assistEnabled) - licenseDetails["scan.config.plugins.standalone"] = strconv.FormatBool(standaloneEnabled) + licenseDetails["scan.config.plugins.cxonedevassist"] = strconv.FormatBool(devAssistEnabled) return licenseDetails, nil } +// containsIgnoreCase returns true if target exists in arr using case-insensitive comparison +func containsIgnoreCase(arr []string, target string) bool { + for _, s := range arr { + if strings.EqualFold(s, target) { + return true + } + } + return false +} + func getJwtStruct() (*JWTStruct, error) { accessToken, err := GetAccessToken() if err != nil { From 35b4d6f0d9481804460731834905f6fc68f2a010 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Fri, 28 Nov 2025 19:33:53 +0530 Subject: [PATCH 16/16] added validaion --- internal/services/realtimeengine/common.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/services/realtimeengine/common.go b/internal/services/realtimeengine/common.go index 2b786746a..e43d07bf0 100644 --- a/internal/services/realtimeengine/common.go +++ b/internal/services/realtimeengine/common.go @@ -34,7 +34,12 @@ func EnsureLicense(jwtWrapper wrappers.JWTWrapper) error { return errors.Wrap(err, "failed to check AIProtectionType engine allowance") } - if aiAllowed || assistAllowed { + devAssistAllowed, err := jwtWrapper.IsAllowedEngine(params.CheckmarxOneStandAloneType) + if err != nil { + return errors.Wrap(err, "failed to check Checkmarx Developer Assist engine allowance") + } + + if aiAllowed || assistAllowed || devAssistAllowed { return nil } return errors.New(errorconstants.ErrMissingAIFeatureLicense)