From b6c790e02d2ca158e411e52155a42c541787cb59 Mon Sep 17 00:00:00 2001 From: Dima R <90623914+cx-dmitri-rivin@users.noreply.github.com> Date: Mon, 9 Feb 2026 10:53:53 +0200 Subject: [PATCH 1/4] done for booth bugs --- internal/commands/scan.go | 28 ++++++++++++++++++++-------- internal/commands/scan_test.go | 13 +++++++++---- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/internal/commands/scan.go b/internal/commands/scan.go index 85944edcf..91ac97fbc 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -2268,7 +2268,7 @@ func definePathForZipFileOrDirectory(cmd *cobra.Command) (zipFile, sourceDir str return zipFile, sourceDir, err } -// enforceLocalResolutionForTarFiles checks if any container image is a tar file +// enforceLocalResolutionForTarFiles checks if any container image is a tar file or oci-dir // and enforces local resolution by setting the --containers-local-resolution flag. // Container-security scan-type related function. func enforceLocalResolutionForTarFiles(cmd *cobra.Command) error { @@ -2289,7 +2289,7 @@ func enforceLocalResolutionForTarFiles(cmd *cobra.Command) error { // Parse container images list containerImagesList := strings.Split(strings.TrimSpace(containerImagesFlag), ",") - hasTarFile := false + needsLocalResolution := false for _, containerImageName := range containerImagesList { // Normalize input: trim spaces and quotes @@ -2303,15 +2303,21 @@ func enforceLocalResolutionForTarFiles(cmd *cobra.Command) error { // Check if this is a tar file by checking if it contains a tar file reference if isTarFileReference(containerImageName) { - hasTarFile = true + needsLocalResolution = true + break + } + + // Check if this is an oci-dir reference - these also require local resolution + if strings.HasPrefix(containerImageName, ociDirPrefix) { + needsLocalResolution = true break } } - // If at least one tar file is found, enforce local resolution - if hasTarFile { - logger.PrintIfVerbose("Detected tar file(s) in --container-images flag") - fmt.Println("Warning: Tar file(s) detected in --container-images. Automatically enabling --containers-local-resolution flag.") + // If at least one tar file or oci-dir is found, enforce local resolution + if needsLocalResolution { + logger.PrintIfVerbose("Detected tar file(s) or oci-dir in --container-images flag") + fmt.Println("Warning: Tar file(s) or oci-dir detected in --container-images. Automatically enabling --containers-local-resolution flag.") // Set the flag to true err := cmd.Flags().Set(commonParams.ContainerResolveLocallyFlag, "true") @@ -3828,13 +3834,19 @@ func validateOCIDirPrefix(imageRef string) error { // 3. Can have optional :tag suffix pathToCheck := imageRef - if strings.Contains(imageRef, ":") { + + // Handle Windows absolute paths (e.g., C:\path\to\dir) before splitting on colons + // Windows paths have a drive letter followed by colon and path separator + if !isWindowsAbsolutePath(imageRef) && strings.Contains(imageRef, ":") { // Handle case like "oci-dir:/path/to/dir:tag" or "oci-dir:name.tar:tag" + // For Unix paths, we can safely split on colon to extract the tag pathParts := strings.Split(imageRef, ":") if len(pathParts) > 0 && pathParts[0] != "" { pathToCheck = pathParts[0] } } + // For Windows absolute paths, use the entire imageRef as pathToCheck + // since the colon is part of the drive letter (e.g., C:\path\to\dir) exists, err := osinstaller.FileExists(pathToCheck) if err != nil { diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index e51614dc4..f88073c93 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -4515,7 +4515,7 @@ func TestIsTarFileReference(t *testing.T) { } } -// TestEnforceLocalResolutionForTarFiles tests the automatic enforcement of local resolution when tar files are detected. +// TestEnforceLocalResolutionForTarFiles tests the automatic enforcement of local resolution when tar files or oci-dir are detected. // Container-security scan-type related test function. func TestEnforceLocalResolutionForTarFiles(t *testing.T) { testCases := []struct { @@ -4530,15 +4530,20 @@ func TestEnforceLocalResolutionForTarFiles(t *testing.T) { {"Already enabled", "alpine.tar", true, true, false}, {"Only image:tag", "nginx:latest,alpine:3.18", false, false, false}, {"Non-tar prefixes", "docker:nginx:latest,registry:ubuntu:22.04", false, false, false}, - {"Invalid tar:tag format", "oci-dir:file.tar:latest", false, false, false}, - // Should enable local resolution + // Should enable local resolution - tar files {"Single tar", "alpine.tar", false, true, true}, {"Mixed tar+image", "nginx:latest,alpine.tar", false, true, true}, {"Tar with spaces/quotes", " 'alpine.tar' ,nginx:latest", false, true, true}, {"Prefixed tar", "docker-archive:alpine.tar", false, true, true}, {"oci-dir tar", "oci-dir:image.tar", false, true, true}, {"Tar at end", "nginx:latest,ubuntu.tar", false, true, true}, + + // Should enable local resolution - oci-dir directories + {"oci-dir directory", "oci-dir:my-alpine-image", false, true, true}, + {"oci-dir with path", "oci-dir:/path/to/oci-layout", false, true, true}, + {"oci-dir with tag suffix", "oci-dir:file.tar:latest", false, true, true}, + {"Mixed oci-dir+image", "nginx:latest,oci-dir:my-image", false, true, true}, } for _, tc := range testCases { @@ -4578,7 +4583,7 @@ func TestEnforceLocalResolutionForTarFiles(t *testing.T) { t.Errorf("Expected local resolution=%v, got=%v", tc.expectedLocalResolution, actualLocalResolution) } - hasWarning := strings.Contains(output, "Warning:") && strings.Contains(output, "Tar file") + hasWarning := strings.Contains(output, "Warning:") && (strings.Contains(output, "Tar file") || strings.Contains(output, "oci-dir")) if tc.expectWarning && !hasWarning { t.Errorf("Expected warning but got: %s", output) } else if !tc.expectWarning && hasWarning { From 24508afc92f1b1396e8a2125414225a1c0ada57e Mon Sep 17 00:00:00 2001 From: Dima R <90623914+cx-dmitri-rivin@users.noreply.github.com> Date: Sun, 15 Feb 2026 14:39:03 +0200 Subject: [PATCH 2/4] Fix oci-dir Windows path validation (AST-130779) Route all prefixed inputs (oci-dir:, docker:, etc.) directly to their prefix-specific validators to fix Windows absolute path handling. Previously, oci-dir:C:\path\to\dir was misrouted through tar file validation causing "not a valid tar file" errors. - Move prefix validation check before file path detection - Remove unreachable dead code (23 lines) - Add Windows full path test coverage (C:\, D:\ variations) - Both AST-130779 and AST-130781 are now fixed Co-authored-by: Cursor --- internal/commands/scan.go | 33 ++++++++++----------------------- internal/commands/scan_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/internal/commands/scan.go b/internal/commands/scan.go index 91ac97fbc..58bb20f9f 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -3629,6 +3629,11 @@ func validateContainerImageFormat(containerImage string) error { sanitizedInput = containerImage } + // Route prefixed inputs (oci-dir:, docker:, etc.) directly to their specific validators + if hasKnownSource { + return validatePrefixedContainerImage(containerImage, getPrefixFromInput(containerImage, knownSources)) + } + // Check if this looks like a file path before parsing colons if looksLikeFilePath(sanitizedInput) { return validateFilePath(sanitizedInput) @@ -3647,11 +3652,6 @@ func validateContainerImageFormat(containerImage string) error { return errors.Errorf("Invalid value for --container-images flag. Image name and tag cannot be empty. Found: image='%s', tag='%s'", imageName, imageTag) } - // For prefixed inputs, also validate the prefix-specific requirements - if hasKnownSource { - return validatePrefixedContainerImage(containerImage, getPrefixFromInput(containerImage, knownSources)) - } - // Check if this looks like an invalid prefix attempt (e.g., "invalid-prefix:file.tar") // If the "tag" ends with .tar and the "image name" looks like a simple prefix (no / or .) // then the user likely intended to use a prefix format but used an unknown prefix @@ -3687,20 +3687,7 @@ func validateContainerImageFormat(containerImage string) error { return errors.Errorf("%s: image does not have a tag. Did you try to scan a tar file?", containerImagesFlagError) } - // Step 4: Special handling for prefixes that don't require tags (e.g., oci-dir:) - if hasKnownSource { - prefix := getPrefixFromInput(containerImage, knownSources) - // oci-dir can reference directories without tags, validate it - if prefix == ociDirPrefix { - return validatePrefixedContainerImage(containerImage, prefix) - } - // Archive prefixes (file:, docker-archive:, oci-archive:) can reference files without tags - if prefix == filePrefix || prefix == dockerArchivePrefix || prefix == ociArchivePrefix { - return validatePrefixedContainerImage(containerImage, prefix) - } - } - - // Step 5: Not a tar file, no special prefix, and no colon - assume user forgot to add tag (error) + // Step 4: Not a tar file, no special prefix, and no colon - assume user forgot to add tag (error) return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) } @@ -3790,7 +3777,7 @@ func validatePrefixedContainerImage(containerImage, prefix string) error { imageRef = strings.Trim(imageRef, "'\"") if imageRef == "" { - return errors.Errorf("Invalid value for --container-images flag. After prefix '%s', the image reference cannot be empty", prefix) + return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) } // Delegate to specific validators based on prefix type @@ -3872,7 +3859,7 @@ func validateRegistryPrefix(imageRef string) error { // Basic validation - should not be empty and should not be obviously just a registry URL if strings.HasSuffix(imageRef, ".com") || strings.HasSuffix(imageRef, ".io") || strings.HasSuffix(imageRef, ".org") || strings.HasSuffix(imageRef, ".net") { - return errors.Errorf("Invalid value for --container-images flag. Registry format must specify a single image, not just a registry URL. Use format: registry:/: or registry::") + return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) } // Check for registry:host:port format (just registry URL with port) @@ -3880,7 +3867,7 @@ func validateRegistryPrefix(imageRef string) error { parts := strings.Split(imageRef, ":") if len(parts) == minImagePartsWithTag && len(parts[portPartIndex]) <= maxPortLength && !strings.Contains(imageRef, "/") { // This looks like registry:port format without image - return errors.Errorf("Invalid value for --container-images flag. Registry format must specify a single image, not just a registry URL. Use format: registry:/:") + return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) } } @@ -3896,7 +3883,7 @@ func validateDaemonPrefix(imageRef, prefix string) error { imageParts := strings.Split(imageRef, ":") if len(imageParts) < minImagePartsWithTag || imageParts[imageNameIndex] == "" || imageParts[imageTagIndex] == "" { - return errors.Errorf("Invalid value for --container-images flag. Prefix '%s' expects format :", prefix) + return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) } return nil } diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index f88073c93..4d45676fa 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -2796,6 +2796,22 @@ func TestValidateContainerImageFormat_Comprehensive(t *testing.T) { expectedError: "", setupFiles: []string{"image.tar"}, }, + // Windows full path tests + { + name: "oci-dir with Windows full path - C drive backslash", + containerImage: "oci-dir:C:\\Users\\test\\docker.io\\library\\alpine", + expectedError: "--container-images flag error: path C:\\Users\\test\\docker.io\\library\\alpine does not exist", + }, + { + name: "oci-dir with Windows full path - C drive forward slash", + containerImage: "oci-dir:C:/Users/test/docker.io/library/alpine", + expectedError: "--container-images flag error: path C:/Users/test/docker.io/library/alpine does not exist", + }, + { + name: "oci-dir with Windows full path - D drive", + containerImage: "oci-dir:D:\\data\\images\\my-image", + expectedError: "--container-images flag error: path D:\\data\\images\\my-image does not exist", + }, // ==================== Dir Prefix (Forbidden) ==================== { From ed0a3c69807f1178d3b40855316b8651153947b8 Mon Sep 17 00:00:00 2001 From: Dima R <90623914+cx-dmitri-rivin@users.noreply.github.com> Date: Tue, 17 Feb 2026 08:20:36 +0200 Subject: [PATCH 3/4] containers resolver upgrade --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5982ec7f1..d0d0ca3f6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/checkmarx/ast-cli go 1.24.11 require ( - github.com/Checkmarx/containers-resolver v1.0.31 + github.com/Checkmarx/containers-resolver v1.0.32 github.com/Checkmarx/containers-types v1.0.9 github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63 github.com/Checkmarx/gen-ai-wrapper v1.0.3 From 55b5c70f07db85cebb43cb8244daa23c9e8cb307 Mon Sep 17 00:00:00 2001 From: Dima R <90623914+cx-dmitri-rivin@users.noreply.github.com> Date: Tue, 17 Feb 2026 08:26:09 +0200 Subject: [PATCH 4/4] revert of the error logs and syft update --- go.mod | 2 +- go.sum | 8 ++++---- internal/commands/scan.go | 8 ++++---- internal/commands/scan_test.go | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index d0d0ca3f6..33ad5b7c2 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( github.com/BobuSumisu/aho-corasick v1.0.3 // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/Checkmarx/containers-images-extractor v1.0.22 - github.com/Checkmarx/containers-syft-packages-extractor v1.0.23 // indirect + github.com/Checkmarx/containers-syft-packages-extractor v1.0.24 // indirect github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect github.com/DataDog/zstd v1.5.6 // indirect github.com/Masterminds/goutils v1.1.1 // indirect diff --git a/go.sum b/go.sum index 5558ed23c..9659c579f 100644 --- a/go.sum +++ b/go.sum @@ -67,10 +67,10 @@ github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Checkmarx/containers-images-extractor v1.0.22 h1:kJZgwk28LwJZ7Xky+kzwL+JSZOlpwrGsZQhhz4L2t6s= github.com/Checkmarx/containers-images-extractor v1.0.22/go.mod h1:HyzVb8TtTDf56hGlSakalPXtzjJ6VhTYe9fmAcOS+V8= -github.com/Checkmarx/containers-resolver v1.0.31 h1:Xd4D9rvGxNXc0STHZdIWtQC4SkrD65MLpU+S6H9tb/0= -github.com/Checkmarx/containers-resolver v1.0.31/go.mod h1:hQ5lw0dCc+va4jm47TpeVqRzU71/SpoE+T3e69vCZwI= -github.com/Checkmarx/containers-syft-packages-extractor v1.0.23 h1:qP4OBlCVF6BbOO0gzcoOzAtfdx7+M1kU3OsY2xBvy8E= -github.com/Checkmarx/containers-syft-packages-extractor v1.0.23/go.mod h1:OPGYISPnKtVFl2mZrClErv83ZLjUPKjdQQsXLmx++oY= +github.com/Checkmarx/containers-resolver v1.0.32 h1:clCWHZ2hCgBvEudLkmelxuTsS30XS0U+Wbhr7dKVJbs= +github.com/Checkmarx/containers-resolver v1.0.32/go.mod h1:qW1Na7dekGfJNf3fm6tnVTzHbw/FJj7nSBnPYc4YTvI= +github.com/Checkmarx/containers-syft-packages-extractor v1.0.24 h1:+BxJgYGD6olWFaQ6B+a1JHMJHYjbRokhT1/j1N/CnuE= +github.com/Checkmarx/containers-syft-packages-extractor v1.0.24/go.mod h1:OPGYISPnKtVFl2mZrClErv83ZLjUPKjdQQsXLmx++oY= github.com/Checkmarx/containers-types v1.0.9 h1:LbHDj9LZ0x3f28wDx398WC19sw0U0EfEewHMLStBwvs= github.com/Checkmarx/containers-types v1.0.9/go.mod h1:KR0w8XCosq3+6jRCfQrH7i//Nj2u11qaUJM62CREFZA= github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63 h1:SCuTcE+CFvgjbIxUNL8rsdB2sAhfuNx85HvxImKta3g= diff --git a/internal/commands/scan.go b/internal/commands/scan.go index c5cfd6476..9f3ecba7e 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -3780,7 +3780,7 @@ func validatePrefixedContainerImage(containerImage, prefix string) error { imageRef = strings.Trim(imageRef, "'\"") if imageRef == "" { - return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) + return errors.Errorf("Invalid value for --container-images flag. After prefix '%s', the image reference cannot be empty", prefix) } // Delegate to specific validators based on prefix type @@ -3862,7 +3862,7 @@ func validateRegistryPrefix(imageRef string) error { // Basic validation - should not be empty and should not be obviously just a registry URL if strings.HasSuffix(imageRef, ".com") || strings.HasSuffix(imageRef, ".io") || strings.HasSuffix(imageRef, ".org") || strings.HasSuffix(imageRef, ".net") { - return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) + return errors.Errorf("Invalid value for --container-images flag. Registry format must specify a single image, not just a registry URL. Use format: registry:/: or registry::") } // Check for registry:host:port format (just registry URL with port) @@ -3870,7 +3870,7 @@ func validateRegistryPrefix(imageRef string) error { parts := strings.Split(imageRef, ":") if len(parts) == minImagePartsWithTag && len(parts[portPartIndex]) <= maxPortLength && !strings.Contains(imageRef, "/") { // This looks like registry:port format without image - return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) + return errors.Errorf("Invalid value for --container-images flag. Registry format must specify a single image, not just a registry URL. Use format: registry:/:") } } @@ -3886,7 +3886,7 @@ func validateDaemonPrefix(imageRef, prefix string) error { imageParts := strings.Split(imageRef, ":") if len(imageParts) < minImagePartsWithTag || imageParts[imageNameIndex] == "" || imageParts[imageTagIndex] == "" { - return errors.Errorf("%s: image does not have a tag", containerImagesFlagError) + return errors.Errorf("Invalid value for --container-images flag. Prefix '%s' expects format :", prefix) } return nil } diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index 4d45676fa..1c20053f5 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -2717,12 +2717,12 @@ func TestValidateContainerImageFormat_Comprehensive(t *testing.T) { { name: "Invalid docker prefix - missing tag", containerImage: "docker:nginx", - expectedError: "image does not have a tag", + expectedError: "Prefix 'docker:' expects format :", }, { name: "Invalid docker prefix - empty", containerImage: "docker:", - expectedError: "image does not have a tag", + expectedError: "After prefix 'docker:', the image reference cannot be empty", }, // ==================== Podman Daemon Tests ==================== @@ -2734,7 +2734,7 @@ func TestValidateContainerImageFormat_Comprehensive(t *testing.T) { { name: "Invalid podman prefix - missing tag", containerImage: "podman:alpine", - expectedError: "image does not have a tag", + expectedError: "Prefix 'podman:' expects format :", }, // ==================== Containerd Daemon Tests ==================== @@ -2746,7 +2746,7 @@ func TestValidateContainerImageFormat_Comprehensive(t *testing.T) { { name: "Invalid containerd prefix - missing tag", containerImage: "containerd:nginx", - expectedError: "image does not have a tag", + expectedError: "Prefix 'containerd:' expects format :", }, // ==================== Registry Tests ==================== @@ -2763,7 +2763,7 @@ func TestValidateContainerImageFormat_Comprehensive(t *testing.T) { { name: "Invalid registry - just URL without image", containerImage: "registry:myregistry.com", - expectedError: "image does not have a tag", + expectedError: "Registry format must specify a single image, not just a registry URL", }, // ==================== OCI-Dir Tests ====================