Skip to content

Commit ba71598

Browse files
Merge branch 'main' of https://github.com/Checkmarx/ast-cli into feature/ast-105749-sbom-scan
2 parents 432b7ab + ba573ed commit ba71598

File tree

7 files changed

+121
-33
lines changed

7 files changed

+121
-33
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/checkmarx/ast-cli
33
go 1.24.4
44

55
require (
6-
github.com/Checkmarx/containers-resolver v1.0.15
6+
github.com/Checkmarx/containers-resolver v1.0.19
77
github.com/Checkmarx/containers-types v1.0.9
88
github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63
99
github.com/Checkmarx/gen-ai-wrapper v1.0.2
@@ -42,7 +42,7 @@ require (
4242
github.com/BobuSumisu/aho-corasick v1.0.3 // indirect
4343
github.com/BurntSushi/toml v1.5.0 // indirect
4444
github.com/Checkmarx/containers-images-extractor v1.0.17
45-
github.com/Checkmarx/containers-syft-packages-extractor v1.0.13 // indirect
45+
github.com/Checkmarx/containers-syft-packages-extractor v1.0.15 // indirect
4646
github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect
4747
github.com/DataDog/zstd v1.5.6 // indirect
4848
github.com/Masterminds/goutils v1.1.1 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
6565
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
6666
github.com/Checkmarx/containers-images-extractor v1.0.17 h1:lzisdh50nR5yzTjTkT9r9dlHHI7aC72XTGjTp35KqHM=
6767
github.com/Checkmarx/containers-images-extractor v1.0.17/go.mod h1:hRXOiq6Vw2QiIuxIqV+6+osMk0vvIpoMdTMLyz9OfE8=
68-
github.com/Checkmarx/containers-resolver v1.0.15 h1:cm4d6vYWi6G9J9vnAw+dWcMsJwEFMo+anCHVaSp0nMQ=
69-
github.com/Checkmarx/containers-resolver v1.0.15/go.mod h1:9mdw8elUHj9NO9+ejjuuuCByfxvx9mG+JTJxDLi9ubM=
70-
github.com/Checkmarx/containers-syft-packages-extractor v1.0.13 h1:9ah0rruMGgRiug/bD/JJDSrDqEqS7sKGVdc5sqbkwk8=
71-
github.com/Checkmarx/containers-syft-packages-extractor v1.0.13/go.mod h1:EFeB4//lO4KMVj9+eMg6z5jnO9F1e1T4jUoIcx0/19M=
68+
github.com/Checkmarx/containers-resolver v1.0.19 h1:OqPJq3dL0vv8BC2Qco6/VTqmg1Jurk32Yf/bW9cZuq8=
69+
github.com/Checkmarx/containers-resolver v1.0.19/go.mod h1:UwT3Z+rf6RZv1voMt1xtEctWguhQrzHk1dhEb0Dl5fY=
70+
github.com/Checkmarx/containers-syft-packages-extractor v1.0.15 h1:yM7Plt86oL47Kijr1fwsrWwuACNTwWgxZSZ/lifXTlk=
71+
github.com/Checkmarx/containers-syft-packages-extractor v1.0.15/go.mod h1:Jr3dQVFslMCJ+8orsF1orFn05cO3mprUy5b43yn0IIM=
7272
github.com/Checkmarx/containers-types v1.0.9 h1:LbHDj9LZ0x3f28wDx398WC19sw0U0EfEewHMLStBwvs=
7373
github.com/Checkmarx/containers-types v1.0.9/go.mod h1:KR0w8XCosq3+6jRCfQrH7i//Nj2u11qaUJM62CREFZA=
7474
github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63 h1:SCuTcE+CFvgjbIxUNL8rsdB2sAhfuNx85HvxImKta3g=

internal/commands/scan.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,8 @@ func addContainersScan(cmd *cobra.Command, resubmitConfig []wrappers.Config) (ma
11321132
containerMapConfig[resultsMapType] = commonParams.ContainersType
11331133
containerConfig := wrappers.ContainerConfig{}
11341134

1135-
initializeContainersConfigWithResubmitValues(resubmitConfig, &containerConfig)
1135+
containerResolveLocally, _ := cmd.Flags().GetBool(commonParams.ContainerResolveLocallyFlag)
1136+
initializeContainersConfigWithResubmitValues(resubmitConfig, &containerConfig, containerResolveLocally)
11361137

11371138
fileFolderFilter, _ := cmd.PersistentFlags().GetString(commonParams.ContainersFileFolderFilterFlag)
11381139
if fileFolderFilter != "" {
@@ -1151,7 +1152,7 @@ func addContainersScan(cmd *cobra.Command, resubmitConfig []wrappers.Config) (ma
11511152
containerConfig.ImagesFilter = imageTagFilter
11521153
}
11531154
userCustomImages, _ := cmd.Flags().GetString(commonParams.ContainerImagesFlag)
1154-
if userCustomImages != "" {
1155+
if userCustomImages != "" && !containerResolveLocally {
11551156
containerImagesList := strings.Split(strings.TrimSpace(userCustomImages), ",")
11561157
for _, containerImageName := range containerImagesList {
11571158
if containerImagesErr := validateContainerImageFormat(containerImageName); containerImagesErr != nil {
@@ -1166,7 +1167,7 @@ func addContainersScan(cmd *cobra.Command, resubmitConfig []wrappers.Config) (ma
11661167
return containerMapConfig, nil
11671168
}
11681169

1169-
func initializeContainersConfigWithResubmitValues(resubmitConfig []wrappers.Config, containerConfig *wrappers.ContainerConfig) {
1170+
func initializeContainersConfigWithResubmitValues(resubmitConfig []wrappers.Config, containerConfig *wrappers.ContainerConfig, containerResolveLocally bool) {
11701171
for _, config := range resubmitConfig {
11711172
if config.Type != commonParams.ContainersType {
11721173
continue
@@ -1188,7 +1189,7 @@ func initializeContainersConfigWithResubmitValues(resubmitConfig []wrappers.Conf
11881189
containerConfig.ImagesFilter = resubmitImagesFilter.(string)
11891190
}
11901191
resubmitUserCustomImages := config.Value[ConfigUserCustomImagesKey]
1191-
if resubmitUserCustomImages != nil && resubmitUserCustomImages != "" {
1192+
if resubmitUserCustomImages != nil && resubmitUserCustomImages != "" && !containerResolveLocally {
11921193
containerConfig.UserCustomImages = resubmitUserCustomImages.(string)
11931194
}
11941195
}
@@ -1753,7 +1754,7 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
17531754

17541755
if isSingleContainerScanTriggered() && containerResolveLocally {
17551756
logger.PrintIfVerbose("Single container scan triggered: compressing only the container resolution file")
1756-
containerResolutionFilePath := filepath.Join(directoryPath, containerResolutionFileName)
1757+
containerResolutionFilePath := filepath.Join(directoryPath, ".checkmarx", "containers", containerResolutionFileName)
17571758
zipFilePath, dirPathErr = util.CompressFile(containerResolutionFilePath, containerResolutionFileName, directoryCreationPrefix)
17581759
} else if isSingleContainerScanTriggered() && containerImagesFlag != "" {
17591760
logger.PrintIfVerbose("Single container scan with external images: creating minimal zip file")

internal/commands/scan_test.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,12 +1855,13 @@ func TestAddContainersScan_WithCustomImages_ShouldSetUserCustomImages(t *testing
18551855
func TestInitializeContainersConfigWithResubmitValues_UserCustomImages(t *testing.T) {
18561856
// Define test cases
18571857
testCases := []struct {
1858-
name string
1859-
resubmitConfig []wrappers.Config
1860-
expectedCustomImages string
1858+
name string
1859+
resubmitConfig []wrappers.Config
1860+
containerResolveLocally bool
1861+
expectedCustomImages string
18611862
}{
18621863
{
1863-
name: "When UserCustomImages is valid string, it should be set in containerConfig",
1864+
name: "When UserCustomImages is valid string and ContainerResolveLocally is false, it should be set in containerConfig",
18641865
resubmitConfig: []wrappers.Config{
18651866
{
18661867
Type: commonParams.ContainersType,
@@ -1869,7 +1870,21 @@ func TestInitializeContainersConfigWithResubmitValues_UserCustomImages(t *testin
18691870
},
18701871
},
18711872
},
1872-
expectedCustomImages: "image1:tag1,image2:tag2",
1873+
containerResolveLocally: false,
1874+
expectedCustomImages: "image1:tag1,image2:tag2",
1875+
},
1876+
{
1877+
name: "When UserCustomImages is valid string and ContainerResolveLocally is true, it should not be set in containerConfig",
1878+
resubmitConfig: []wrappers.Config{
1879+
{
1880+
Type: commonParams.ContainersType,
1881+
Value: map[string]interface{}{
1882+
ConfigUserCustomImagesKey: "image1:tag1,image2:tag2",
1883+
},
1884+
},
1885+
},
1886+
containerResolveLocally: true,
1887+
expectedCustomImages: "",
18731888
},
18741889
{
18751890
name: "When UserCustomImages is empty string, containerConfig should not be updated",
@@ -1881,7 +1896,8 @@ func TestInitializeContainersConfigWithResubmitValues_UserCustomImages(t *testin
18811896
},
18821897
},
18831898
},
1884-
expectedCustomImages: "",
1899+
containerResolveLocally: false,
1900+
expectedCustomImages: "",
18851901
},
18861902
{
18871903
name: "When UserCustomImages is nil, containerConfig should not be updated",
@@ -1893,7 +1909,8 @@ func TestInitializeContainersConfigWithResubmitValues_UserCustomImages(t *testin
18931909
},
18941910
},
18951911
},
1896-
expectedCustomImages: "",
1912+
containerResolveLocally: false,
1913+
expectedCustomImages: "",
18971914
},
18981915
{
18991916
name: "When config.Value doesn't have UserCustomImages key, containerConfig should not be updated",
@@ -1903,7 +1920,8 @@ func TestInitializeContainersConfigWithResubmitValues_UserCustomImages(t *testin
19031920
Value: map[string]interface{}{},
19041921
},
19051922
},
1906-
expectedCustomImages: "",
1923+
containerResolveLocally: false,
1924+
expectedCustomImages: "",
19071925
},
19081926
}
19091927

@@ -1914,7 +1932,7 @@ func TestInitializeContainersConfigWithResubmitValues_UserCustomImages(t *testin
19141932
containerConfig := &wrappers.ContainerConfig{}
19151933

19161934
// Call the function under test
1917-
initializeContainersConfigWithResubmitValues(tc.resubmitConfig, containerConfig)
1935+
initializeContainersConfigWithResubmitValues(tc.resubmitConfig, containerConfig, tc.containerResolveLocally)
19181936

19191937
// Assert the result
19201938
assert.Equal(t, tc.expectedCustomImages, containerConfig.UserCustomImages,

internal/services/realtimeengine/containersrealtime/containers-realtime.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"github.com/pkg/errors"
1616
)
1717

18+
const defaultTag = "latest"
19+
1820
// ContainersRealtimeService is the service responsible for performing real-time container scanning.
1921
type ContainersRealtimeService struct {
2022
JwtWrapper wrappers.JWTWrapper
@@ -218,26 +220,31 @@ func mergeImagesToResults(listOfImages []wrappers.ContainerImageResponseItem, re
218220

219221
func getImageLocations(images *[]types.ImageModel, imageName, imageTag string) (location []realtimeengine.Location, filePath string) {
220222
for i, img := range *images {
221-
if img.Name == imageName+":"+imageTag || img.Name == imageName+"@"+imageTag {
222-
location := convertLocations(&img.ImageLocations)
223-
filePath := ""
224-
if len(img.ImageLocations) > 0 {
225-
filePath = img.ImageLocations[0].Path
226-
}
227-
*images = append((*images)[:i], (*images)[i+1:]...)
228-
return location, filePath
223+
if !isSameImage(img.Name, imageName, imageTag) {
224+
continue
229225
}
226+
location := convertLocations(&img.ImageLocations)
227+
filePath := ""
228+
if len(img.ImageLocations) > 0 {
229+
filePath = img.ImageLocations[0].Path
230+
}
231+
*images = append((*images)[:i], (*images)[i+1:]...)
232+
return location, filePath
230233
}
231234
return []realtimeengine.Location{}, ""
232235
}
233236

237+
func isSameImage(curImage, imageName, imageTag string) bool {
238+
return curImage == imageName+":"+imageTag || curImage == imageName+"@"+imageTag || curImage == imageName && imageTag == defaultTag
239+
}
240+
234241
// splitToImageAndTag splits the image string into name and tag components.
235242
func splitToImageAndTag(image string) (imageName, imageTag string) {
236243
// Split the image string by the last colon to separate name and tag
237244
lastColonIndex := strings.LastIndex(image, ":")
238245

239246
if lastColonIndex == len(image)-1 || lastColonIndex == -1 {
240-
return image, "latest" // No tag specified, default to "latest"
247+
return image, defaultTag // No tag specified, default to "latest"
241248
}
242249

243250
imageName = image[:lastColonIndex]

internal/services/realtimeengine/secretsrealtime/secrets-realtime.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package secretsrealtime
33
import (
44
"encoding/json"
55
"fmt"
6+
"os"
7+
68
errorconstants "github.com/checkmarx/ast-cli/internal/constants/errors"
79
"github.com/checkmarx/ast-cli/internal/logger"
8-
"os"
910

1011
"github.com/checkmarx/2ms/v3/lib/reporting"
1112
"github.com/checkmarx/2ms/v3/lib/secrets"
@@ -22,6 +23,7 @@ const (
2223
criticalSeverity = "Critical"
2324
highSeverity = "High"
2425
mediumSeverity = "Medium"
26+
genericAPIKey = "generic-api-key"
2527
)
2628

2729
type SecretsRealtimeService struct {
@@ -105,6 +107,8 @@ func (s *SecretsRealtimeService) RunSecretsRealtimeScan(filePath, ignoredFilePat
105107
}
106108

107109
results := convertToSecretsRealtimeResult(report)
110+
resultsPerLineMap := createResultsPerLineMap(results)
111+
results = filterGenericAPIKeyVulIfNeeded(results, resultsPerLineMap)
108112

109113
if ignoredFilePath == "" {
110114
return results, nil
@@ -177,3 +181,38 @@ func getSeverity(secret *secrets.Secret) string {
177181
return highSeverity
178182
}
179183
}
184+
185+
func createResultsPerLineMap(results []SecretsRealtimeResult) map[string][]SecretsRealtimeResult {
186+
resultsPerLine := make(map[string][]SecretsRealtimeResult)
187+
for _, result := range results {
188+
for _, location := range result.Locations {
189+
lineKey := fmt.Sprintf("%s:%d", result.FilePath, location.Line)
190+
resultsPerLine[lineKey] = append(resultsPerLine[lineKey], result)
191+
}
192+
}
193+
return resultsPerLine
194+
}
195+
196+
func filterGenericAPIKeyVulIfNeeded(
197+
results []SecretsRealtimeResult,
198+
resultsPerLine map[string][]SecretsRealtimeResult,
199+
) []SecretsRealtimeResult {
200+
if len(results) == 0 || len(resultsPerLine) == 0 {
201+
return results
202+
}
203+
204+
var filtered []SecretsRealtimeResult
205+
for _, entries := range resultsPerLine {
206+
if len(entries) <= 1 {
207+
filtered = append(filtered, entries...)
208+
continue
209+
}
210+
211+
for i := 0; i < len(entries); i++ {
212+
if entries[i].Title != genericAPIKey {
213+
filtered = append(filtered, entries[i])
214+
}
215+
}
216+
}
217+
return filtered
218+
}

internal/services/realtimeengine/secretsrealtime/secrets-realtime_test.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ func TestRunSecretsRealtimeScan_WithIgnoreFile_FiltersResult(t *testing.T) {
7373
tempDir := t.TempDir()
7474

7575
testFile := filepath.Join(tempDir, "test.txt")
76-
testContent := "aws_access_key_id = AKIAIOSFODNN7EXAMPLE\ngithub_token = ghp_XXXXXXXXXXXXXXXXXXXX"
76+
testContent := "pat = \"ghp_1234567890abcdef1234567890abcdef12345678\""
7777
assert.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644))
7878

7979
ignoreFile := filepath.Join(tempDir, "ignored.json")
8080
ignored := []IgnoredSecret{
81-
{Title: "github-token", FilePath: "test.txt", Line: 2},
81+
{Title: "github-pat", FilePath: testFile, Line: 0},
8282
}
8383
data, _ := json.Marshal(ignored)
8484
assert.NoError(t, os.WriteFile(ignoreFile, data, 0644))
@@ -93,7 +93,30 @@ func TestRunSecretsRealtimeScan_WithIgnoreFile_FiltersResult(t *testing.T) {
9393
assert.NotNil(t, results)
9494

9595
for _, r := range results {
96-
assert.NotEqual(t, "github-token", r.Title)
96+
assert.NotEqual(t, "github-pat", r.Title)
97+
}
98+
}
99+
100+
func TestRunSecretsRealtimeScan_PatVulAndGenericVul_ReturnedOnlyPathVul(t *testing.T) {
101+
mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.OssRealtimeEnabled, Status: true}
102+
103+
tempDir := t.TempDir()
104+
105+
testFile := filepath.Join(tempDir, "test.txt")
106+
testContent := "token = \"ghp_1234567890abcdef1234567890abcdef12345678\""
107+
assert.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644))
108+
109+
service := &SecretsRealtimeService{
110+
JwtWrapper: &mock.JWTMockWrapper{},
111+
FeatureFlagWrapper: &mock.FeatureFlagsMockWrapper{},
112+
}
113+
114+
results, err := service.RunSecretsRealtimeScan(testFile, "")
115+
assert.NoError(t, err)
116+
assert.NotNil(t, results)
117+
118+
for _, r := range results {
119+
assert.Equal(t, "github-pat", r.Title)
97120
}
98121
}
99122

0 commit comments

Comments
 (0)