From bfaa9d3dbd5b6543cd24889eeff5e3c4e115a18f Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:42:49 +0530 Subject: [PATCH 01/37] aimcp server changes --- .../checkmarx/ast/wrapper/CxConstants.java | 3 ++ .../com/checkmarx/ast/wrapper/CxWrapper.java | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index 5f1b8f62..393de4fd 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -61,6 +61,8 @@ public final class CxConstants { static final String ADDITONAL_PARAMS = "--additional-params"; static final String ENGINE = "--engine"; static final String SUB_CMD_KICS_REALTIME = "kics-realtime"; + static final String SUB_CMD_OSS_REALTIME = "oss-realtime"; + static final String IGNORED_FILE_PATH = "--ignored-file-path"; static final String SCA_REMEDIATION_PACKAGE_FILES = "--package-files"; static final String SCA_REMEDIATION_PACKAGE = "--package"; static final String SCA_REMEDIATION_PACKAGE_VERSION = "--package-version"; @@ -74,4 +76,5 @@ public final class CxConstants { static final String SUB_CMD_LEARN_MORE = "learn-more"; static final String SUB_CMD_TENANT = "tenant"; static final String IDE_SCANS_KEY = "scan.config.plugins.ideScans"; + static final String AI_MCP_SERVER_KEY = "scan.config.plugins.aiMcpServer"; } diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 38640d81..a7a2a508 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -404,6 +404,26 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String return Execution.executeCommand(withConfigArguments(arguments), logger, KicsRealtimeResults::fromLine); } + public String ossRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + throws IOException, InterruptedException, CxException { + this.logger.info("Executing 'scan oss-realtime' command using the CLI."); + this.logger.info("Source: {} IgnoredFilePath: {}", sourcePath, ignoredFilePath); + List arguments = new ArrayList<>(); + arguments.add(CxConstants.CMD_SCAN); + arguments.add(CxConstants.SUB_CMD_OSS_REALTIME); + arguments.add(CxConstants.SOURCE); + arguments.add(sourcePath); + if (StringUtils.isNotBlank(ignoredFilePath)) { + arguments.add(CxConstants.IGNORED_FILE_PATH); + arguments.add(ignoredFilePath); + } + return Execution.executeCommand(withConfigArguments(arguments), logger, line -> line); + } + + public String ossRealtimeScan(@NonNull String sourcePath) + throws IOException, InterruptedException, CxException { + return ossRealtimeScan(sourcePath, null); + } public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFile, String engine,String similarityIds) throws IOException, InterruptedException, CxException { this.logger.info("Executing 'remediation kics' command using the CLI."); @@ -455,6 +475,18 @@ public boolean ideScansEnabled() throws CxException, IOException, InterruptedExc .orElse(false); } + public boolean aiMcpServerEnabled() throws CxException, IOException, InterruptedException { + List tenantSettings = tenantSettings(); + if (tenantSettings == null) { + throw new CxException(1, "Unable to parse tenant settings"); + } + return tenantSettings.stream() + .filter(t -> t.getKey().equals(CxConstants.AI_MCP_SERVER_KEY)) + .findFirst() + .map(t -> Boolean.parseBoolean(t.getValue())) + .orElse(false); + } + public List tenantSettings() throws CxException, IOException, InterruptedException { List arguments = jsonArguments(); From 994bec49661cb86a0150ed33bb8d3025dabee6b3 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:11:07 +0530 Subject: [PATCH 02/37] oss-realtime scanner changes --- .../ast/ossrealtime/OssRealtimeLocation.java | 31 ++++++++++++ .../ast/ossrealtime/OssRealtimeResults.java | 50 +++++++++++++++++++ .../ossrealtime/OssRealtimeScanPackage.java | 49 ++++++++++++++++++ .../checkmarx/ast/wrapper/CxConstants.java | 7 ++- .../com/checkmarx/ast/wrapper/CxWrapper.java | 33 +++++++++--- 5 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java create mode 100644 src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java create mode 100644 src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java new file mode 100644 index 00000000..438d10c5 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java @@ -0,0 +1,31 @@ +package com.checkmarx.ast.ossrealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OssRealtimeLocation { + @JsonProperty("Line") + int line; + @JsonProperty("StartIndex") + int startIndex; + @JsonProperty("EndIndex") + int endIndex; + + @JsonCreator + public OssRealtimeLocation(@JsonProperty("Line") int line, + @JsonProperty("StartIndex") int startIndex, + @JsonProperty("EndIndex") int endIndex) { + this.line = line; + this.startIndex = startIndex; + this.endIndex = endIndex; + } +} + diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java new file mode 100644 index 00000000..f7f316c6 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java @@ -0,0 +1,50 @@ +package com.checkmarx.ast.ossrealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OssRealtimeResults { + @JsonProperty("Packages") + List packages; + + @JsonCreator + public OssRealtimeResults(@JsonProperty("Packages") List packages) { + this.packages = packages; + } + + public static OssRealtimeResults fromLine(String line) { + if (StringUtils.isBlank(line)) { + return null; + } + try { + if (isValidJSON(line) && line.contains("\"Packages\"")) { + return new ObjectMapper().readValue(line, OssRealtimeResults.class); + } + } catch (IOException ignored) { + } + return null; + } + + private static boolean isValidJSON(String json) { + try { + new ObjectMapper().readTree(json); + return true; + } catch (IOException e) { + return false; + } + } +} + diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java new file mode 100644 index 00000000..9d7d8479 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java @@ -0,0 +1,49 @@ +package com.checkmarx.ast.ossrealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OssRealtimeScanPackage { + @JsonProperty("PackageManager") + String packageManager; + @JsonProperty("PackageName") + String packageName; + @JsonProperty("PackageVersion") + String packageVersion; + @JsonProperty("FilePath") + String filePath; + @JsonProperty("Locations") + List locations; + @JsonProperty("Status") + String status; + @JsonProperty("Vulnerabilities") + List vulnerabilities; + + @JsonCreator + public OssRealtimeScanPackage(@JsonProperty("PackageManager") String packageManager, + @JsonProperty("PackageName") String packageName, + @JsonProperty("PackageVersion") String packageVersion, + @JsonProperty("FilePath") String filePath, + @JsonProperty("Locations") List locations, + @JsonProperty("Status") String status, + @JsonProperty("Vulnerabilities") List vulnerabilities) { + this.packageManager = packageManager; + this.packageName = packageName; + this.packageVersion = packageVersion; + this.filePath = filePath; + this.locations = locations; + this.status = status; + this.vulnerabilities = vulnerabilities; + } +} + diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index 393de4fd..57611763 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -61,8 +61,6 @@ public final class CxConstants { static final String ADDITONAL_PARAMS = "--additional-params"; static final String ENGINE = "--engine"; static final String SUB_CMD_KICS_REALTIME = "kics-realtime"; - static final String SUB_CMD_OSS_REALTIME = "oss-realtime"; - static final String IGNORED_FILE_PATH = "--ignored-file-path"; static final String SCA_REMEDIATION_PACKAGE_FILES = "--package-files"; static final String SCA_REMEDIATION_PACKAGE = "--package"; static final String SCA_REMEDIATION_PACKAGE_VERSION = "--package-version"; @@ -77,4 +75,9 @@ public final class CxConstants { static final String SUB_CMD_TENANT = "tenant"; static final String IDE_SCANS_KEY = "scan.config.plugins.ideScans"; static final String AI_MCP_SERVER_KEY = "scan.config.plugins.aiMcpServer"; + static final String IGNORED_FILE_PATH = "--ignored-file-path"; + static final String SUB_CMD_OSS_REALTIME = "oss-realtime"; + static final String SUB_CMD_IAC_REALTIME = "iac-realtime"; + static final String SUB_CMD_SECRETS_REALTIME = "secrets-realtime"; + static final String SUB_CMD_CONTAINERS_REALTIME = "containers-realtime"; } diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index a7a2a508..662e035c 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -4,6 +4,7 @@ import com.checkmarx.ast.codebashing.CodeBashing; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; import com.checkmarx.ast.learnMore.LearnMore; +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; import com.checkmarx.ast.predicate.CustomState; import com.checkmarx.ast.predicate.Predicate; import com.checkmarx.ast.project.Project; @@ -404,26 +405,46 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String return Execution.executeCommand(withConfigArguments(arguments), logger, KicsRealtimeResults::fromLine); } - public String ossRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String ignoredFilePath, java.util.function.Function resultParser) throws IOException, InterruptedException, CxException { - this.logger.info("Executing 'scan oss-realtime' command using the CLI."); + this.logger.info("Executing 'scan {}' command using the CLI.", subCommand); this.logger.info("Source: {} IgnoredFilePath: {}", sourcePath, ignoredFilePath); List arguments = new ArrayList<>(); arguments.add(CxConstants.CMD_SCAN); - arguments.add(CxConstants.SUB_CMD_OSS_REALTIME); + arguments.add(subCommand); arguments.add(CxConstants.SOURCE); arguments.add(sourcePath); if (StringUtils.isNotBlank(ignoredFilePath)) { arguments.add(CxConstants.IGNORED_FILE_PATH); arguments.add(ignoredFilePath); } - return Execution.executeCommand(withConfigArguments(arguments), logger, line -> line); + return Execution.executeCommand(withConfigArguments(arguments), logger, resultParser); + } + + // OSS Realtime + public OssRealtimeResults ossRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + throws IOException, InterruptedException, CxException { + return realtimeScan(CxConstants.SUB_CMD_OSS_REALTIME, sourcePath, ignoredFilePath, OssRealtimeResults::fromLine); } - public String ossRealtimeScan(@NonNull String sourcePath) + // IAC Realtime + public String iacRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return ossRealtimeScan(sourcePath, null); + return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath, ignoredFilePath, line -> line); } + + // Secrets Realtime + public String secretsRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + throws IOException, InterruptedException, CxException { + return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, line -> line); + } + + // Containers Realtime + public String containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + throws IOException, InterruptedException, CxException { + return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, ignoredFilePath, line -> line); + } + public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFile, String engine,String similarityIds) throws IOException, InterruptedException, CxException { this.logger.info("Executing 'remediation kics' command using the CLI."); From f741fa1863ff779b45f5c519f73ffb58a791d048 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:16:45 +0530 Subject: [PATCH 03/37] Create OssRealtimeVulnerability.java --- .../ossrealtime/OssRealtimeVulnerability.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java new file mode 100644 index 00000000..857eb292 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java @@ -0,0 +1,19 @@ +package com.checkmarx.ast.ossrealtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OssRealtimeVulnerability { + @JsonProperty("Id") String id; + @JsonProperty("Severity") String severity; + @JsonProperty("Description") String description; + @JsonProperty("FixVersion") String fixVersion; +} + From 1551319d1fe786455b9f430042ecac5caabc8cdd Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:50:57 +0530 Subject: [PATCH 04/37] Unify realtime scan wrappers; consolidate Secrets/IaC models; deprecate and stub obsolete result classes --- .../ContainersRealtimeImage.java | 40 ++++++++ .../ContainersRealtimeResults.java | 54 ++++++++++ .../ContainersRealtimeVulnerability.java | 0 .../ast/iacRealtime/IacRealtimeResults.java | 98 +++++++++++++++++++ .../OssRealtimeResults.java | 9 +- .../OssRealtimeScanPackage.java | 13 +-- .../OssRealtimeVulnerability.java | 2 +- .../RealtimeLocation.java} | 19 ++-- .../SecretsRealtimeResults.java | 95 ++++++++++++++++++ .../com/checkmarx/ast/wrapper/CxWrapper.java | 18 ++-- 10 files changed, 319 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java create mode 100644 src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java create mode 100644 src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java create mode 100644 src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java rename src/main/java/com/checkmarx/ast/{ossrealtime => ossRealtime}/OssRealtimeResults.java (82%) rename src/main/java/com/checkmarx/ast/{ossrealtime => ossRealtime}/OssRealtimeScanPackage.java (82%) rename src/main/java/com/checkmarx/ast/{ossrealtime => ossRealtime}/OssRealtimeVulnerability.java (93%) rename src/main/java/com/checkmarx/ast/{ossrealtime/OssRealtimeLocation.java => realtime/RealtimeLocation.java} (56%) create mode 100644 src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java new file mode 100644 index 00000000..54789e49 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java @@ -0,0 +1,40 @@ +package com.checkmarx.ast.containersRealtime; + +import com.checkmarx.ast.realtime.RealtimeLocation; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +import java.util.Collections; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContainersRealtimeImage { + @JsonProperty("ImageName") String imageName; + @JsonProperty("ImageTag") String imageTag; + @JsonProperty("FilePath") String filePath; + @JsonProperty("Locations") List locations; + @JsonProperty("Status") String status; + @JsonProperty("Vulnerabilities") List vulnerabilities; + + @JsonCreator + public ContainersRealtimeImage(@JsonProperty("ImageName") String imageName, + @JsonProperty("ImageTag") String imageTag, + @JsonProperty("FilePath") String filePath, + @JsonProperty("Locations") List locations, + @JsonProperty("Status") String status, + @JsonProperty("Vulnerabilities") List vulnerabilities) { + this.imageName = imageName; + this.imageTag = imageTag; + this.filePath = filePath; + this.locations = locations == null ? Collections.emptyList() : locations; + this.status = status; + this.vulnerabilities = vulnerabilities == null ? Collections.emptyList() : vulnerabilities; + } +} diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java new file mode 100644 index 00000000..f40747cd --- /dev/null +++ b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java @@ -0,0 +1,54 @@ +package com.checkmarx.ast.containersRealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContainersRealtimeResults { + private static final Logger log = LoggerFactory.getLogger(ContainersRealtimeResults.class); + + @JsonProperty("Images") List images; + + @JsonCreator + public ContainersRealtimeResults(@JsonProperty("Images") List images) { + this.images = images; + } + + public static ContainersRealtimeResults fromLine(String line) { + if (StringUtils.isBlank(line)) { + return null; + } + try { + if (line.contains("\"Images\"") && isValidJSON(line)) { + return new ObjectMapper().readValue(line, ContainersRealtimeResults.class); + } + } catch (IOException e) { + log.debug("Failed to parse containers realtime line: {}", line, e); + } + return null; + } + + private static boolean isValidJSON(String json) { + try { + new ObjectMapper().readTree(json); + return true; + } catch (IOException e) { + return false; + } + } +} + diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java b/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java new file mode 100644 index 00000000..df4dffa8 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java @@ -0,0 +1,98 @@ +package com.checkmarx.ast.iacRealtime; + +import com.checkmarx.ast.realtime.RealtimeLocation; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class IacRealtimeResults { + private static final Logger log = LoggerFactory.getLogger(IacRealtimeResults.class); + @JsonProperty("Results") List results; // Normalized list (array or single object) + + @JsonCreator + public IacRealtimeResults(@JsonProperty("Results") List results) { + this.results = results == null ? Collections.emptyList() : results; + } + + @Value + @JsonDeserialize + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Issue { + @JsonProperty("Title") String title; + @JsonProperty("Description") String description; + @JsonProperty("SimilarityID") String similarityId; + @JsonProperty("FilePath") String filePath; + @JsonProperty("Severity") String severity; + @JsonProperty("ExpectedValue") String expectedValue; + @JsonProperty("ActualValue") String actualValue; + @JsonProperty("Locations") List locations; + + @JsonCreator + public Issue(@JsonProperty("Title") String title, + @JsonProperty("Description") String description, + @JsonProperty("SimilarityID") String similarityId, + @JsonProperty("FilePath") String filePath, + @JsonProperty("Severity") String severity, + @JsonProperty("ExpectedValue") String expectedValue, + @JsonProperty("ActualValue") String actualValue, + @JsonProperty("Locations") List locations) { + this.title = title; + this.description = description; + this.similarityId = similarityId; + this.filePath = filePath; + this.severity = severity; + this.expectedValue = expectedValue; + this.actualValue = actualValue; + this.locations = locations == null ? Collections.emptyList() : locations; + } + } + + public static IacRealtimeResults fromLine(String line) { + if (StringUtils.isBlank(line)) { + return null; + } + try { + if (!isValidJSON(line)) { + return null; + } + ObjectMapper mapper = new ObjectMapper(); + String trimmed = line.trim(); + if (trimmed.startsWith("[")) { + List list = mapper.readValue(trimmed, mapper.getTypeFactory().constructCollectionType(List.class, Issue.class)); + return new IacRealtimeResults(list == null ? Collections.emptyList() : list); + } + if (trimmed.startsWith("{")) { + Issue single = mapper.readValue(trimmed, Issue.class); + return new IacRealtimeResults(Collections.singletonList(single)); + } + } catch (IOException e) { + log.debug("Failed to parse iac realtime JSON line: {}", line, e); + } + return null; + } + + private static boolean isValidJSON(String json) { + try { + new ObjectMapper().readTree(json); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java similarity index 82% rename from src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java rename to src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java index f7f316c6..61a516c9 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossrealtime; +package com.checkmarx.ast.ossRealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -8,6 +8,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Value; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.List; @@ -17,6 +19,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeResults { + private static final Logger log = LoggerFactory.getLogger(OssRealtimeResults.class); @JsonProperty("Packages") List packages; @@ -33,7 +36,8 @@ public static OssRealtimeResults fromLine(String line) { if (isValidJSON(line) && line.contains("\"Packages\"")) { return new ObjectMapper().readValue(line, OssRealtimeResults.class); } - } catch (IOException ignored) { + } catch (IOException e) { + log.debug("Failed to parse oss realtime line: {}", line, e); } return null; } @@ -47,4 +51,3 @@ private static boolean isValidJSON(String json) { } } } - diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java similarity index 82% rename from src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java rename to src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java index 9d7d8479..d8a7149f 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java +++ b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java @@ -1,5 +1,6 @@ -package com.checkmarx.ast.ossrealtime; +package com.checkmarx.ast.ossRealtime; +import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -7,6 +8,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Value; +import java.util.Collections; import java.util.List; @Value @@ -23,7 +25,7 @@ public class OssRealtimeScanPackage { @JsonProperty("FilePath") String filePath; @JsonProperty("Locations") - List locations; + List locations; @JsonProperty("Status") String status; @JsonProperty("Vulnerabilities") @@ -34,16 +36,15 @@ public OssRealtimeScanPackage(@JsonProperty("PackageManager") String packageMana @JsonProperty("PackageName") String packageName, @JsonProperty("PackageVersion") String packageVersion, @JsonProperty("FilePath") String filePath, - @JsonProperty("Locations") List locations, + @JsonProperty("Locations") List locations, @JsonProperty("Status") String status, @JsonProperty("Vulnerabilities") List vulnerabilities) { this.packageManager = packageManager; this.packageName = packageName; this.packageVersion = packageVersion; this.filePath = filePath; - this.locations = locations; + this.locations = locations == null ? Collections.emptyList() : locations; this.status = status; - this.vulnerabilities = vulnerabilities; + this.vulnerabilities = vulnerabilities == null ? Collections.emptyList() : vulnerabilities; } } - diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java similarity index 93% rename from src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java rename to src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java index 857eb292..5379c111 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossrealtime; +package com.checkmarx.ast.ossRealtime; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java b/src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java similarity index 56% rename from src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java rename to src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java index 438d10c5..277fe13d 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java +++ b/src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossrealtime; +package com.checkmarx.ast.realtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -11,18 +11,15 @@ @JsonDeserialize @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public class OssRealtimeLocation { - @JsonProperty("Line") - int line; - @JsonProperty("StartIndex") - int startIndex; - @JsonProperty("EndIndex") - int endIndex; +public class RealtimeLocation { + @JsonProperty("Line") int line; + @JsonProperty("StartIndex") int startIndex; + @JsonProperty("EndIndex") int endIndex; @JsonCreator - public OssRealtimeLocation(@JsonProperty("Line") int line, - @JsonProperty("StartIndex") int startIndex, - @JsonProperty("EndIndex") int endIndex) { + public RealtimeLocation(@JsonProperty("Line") int line, + @JsonProperty("StartIndex") int startIndex, + @JsonProperty("EndIndex") int endIndex) { this.line = line; this.startIndex = startIndex; this.endIndex = endIndex; diff --git a/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java b/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java new file mode 100644 index 00000000..d04d00db --- /dev/null +++ b/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java @@ -0,0 +1,95 @@ +package com.checkmarx.ast.secretsRealtime; + +import com.checkmarx.ast.realtime.RealtimeLocation; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretsRealtimeResults { + private static final Logger log = LoggerFactory.getLogger(SecretsRealtimeResults.class); + + @JsonProperty("Secrets") + List secrets; // Normalized list (array or single object from CLI) + + @JsonCreator + public SecretsRealtimeResults(@JsonProperty("Secrets") List secrets) { + this.secrets = secrets == null ? Collections.emptyList() : secrets; + } + + @Value + @JsonDeserialize + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Secret { + @JsonProperty("Title") String title; + @JsonProperty("Description") String description; + @JsonProperty("SecretValue") String secretValue; + @JsonProperty("FilePath") String filePath; + @JsonProperty("Severity") String severity; + @JsonProperty("Locations") List locations; + + @JsonCreator + public Secret(@JsonProperty("Title") String title, + @JsonProperty("Description") String description, + @JsonProperty("SecretValue") String secretValue, + @JsonProperty("FilePath") String filePath, + @JsonProperty("Severity") String severity, + @JsonProperty("Locations") List locations) { + this.title = title; + this.description = description; + this.secretValue = secretValue; + this.filePath = filePath; + this.severity = severity; + this.locations = locations == null ? Collections.emptyList() : locations; + } + } + + public static SecretsRealtimeResults fromLine(String line) { + if (StringUtils.isBlank(line)) { + return null; // skip blank + } + try { + if (!isValidJSON(line)) { + return null; + } + ObjectMapper mapper = new ObjectMapper(); + String trimmed = line.trim(); + if (trimmed.startsWith("[")) { // array form + List list = mapper.readValue(trimmed, + mapper.getTypeFactory().constructCollectionType(List.class, Secret.class)); + return new SecretsRealtimeResults(list); + } + if (trimmed.startsWith("{")) { // single object form + Secret single = mapper.readValue(trimmed, Secret.class); + return new SecretsRealtimeResults(Collections.singletonList(single)); + } + } catch (IOException e) { + log.debug("Failed to parse secrets realtime JSON line: {}", line, e); + } + return null; + } + + private static boolean isValidJSON(String json) { + try { + new ObjectMapper().readTree(json); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 662e035c..a8662bb9 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -4,7 +4,10 @@ import com.checkmarx.ast.codebashing.CodeBashing; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; import com.checkmarx.ast.learnMore.LearnMore; -import com.checkmarx.ast.ossrealtime.OssRealtimeResults; +import com.checkmarx.ast.ossRealtime.OssRealtimeResults; +import com.checkmarx.ast.secretsRealtime.SecretsRealtimeResults; +import com.checkmarx.ast.iacRealtime.IacRealtimeResults; +import com.checkmarx.ast.containersRealtime.ContainersRealtimeResults; import com.checkmarx.ast.predicate.CustomState; import com.checkmarx.ast.predicate.Predicate; import com.checkmarx.ast.project.Project; @@ -25,7 +28,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import java.lang.reflect.Field; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -428,21 +430,21 @@ public OssRealtimeResults ossRealtimeScan(@NonNull String sourcePath, String ign } // IAC Realtime - public String iacRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public IacRealtimeResults iacRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath, ignoredFilePath, line -> line); + return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath, ignoredFilePath, IacRealtimeResults::fromLine); } // Secrets Realtime - public String secretsRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public SecretsRealtimeResults secretsRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, line -> line); + return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, SecretsRealtimeResults::fromLine); } // Containers Realtime - public String containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public ContainersRealtimeResults containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, ignoredFilePath, line -> line); + return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, ignoredFilePath, ContainersRealtimeResults::fromLine); } public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFile, String engine,String similarityIds) From decc7332a2c4c32d30163dbb1750c00346d32fc8 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:06:23 +0530 Subject: [PATCH 05/37] Add ContainersRealtimeVulnerability model for containers realtime scan parsing --- .../ContainersRealtimeVulnerability.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java index e69de29b..c40d4dda 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java @@ -0,0 +1,17 @@ +package com.checkmarx.ast.containersRealtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContainersRealtimeVulnerability { + @JsonProperty("CVE") String cve; + @JsonProperty("Severity") String severity; +} + From 1ac52860fa42c4c018cdb10fefea7ea9f5e24502 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:09:15 +0530 Subject: [PATCH 06/37] Add @JsonCreator constructor to OssRealtimeVulnerability for reliable Jackson deserialization --- .../ContainersRealtimeVulnerability.java | 9 ++++++++- .../ast/ossRealtime/OssRealtimeVulnerability.java | 14 +++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java index c40d4dda..e1458735 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java @@ -1,5 +1,6 @@ package com.checkmarx.ast.containersRealtime; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -13,5 +14,11 @@ public class ContainersRealtimeVulnerability { @JsonProperty("CVE") String cve; @JsonProperty("Severity") String severity; -} + @JsonCreator + public ContainersRealtimeVulnerability(@JsonProperty("CVE") String cve, + @JsonProperty("Severity") String severity) { + this.cve = cve; + this.severity = severity; + } +} diff --git a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java index 5379c111..00a4618a 100644 --- a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java @@ -1,5 +1,6 @@ package com.checkmarx.ast.ossRealtime; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -10,10 +11,21 @@ @JsonDeserialize @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) + public class OssRealtimeVulnerability { @JsonProperty("Id") String id; @JsonProperty("Severity") String severity; @JsonProperty("Description") String description; @JsonProperty("FixVersion") String fixVersion; -} + @JsonCreator + public OssRealtimeVulnerability(@JsonProperty("Id") String id, + @JsonProperty("Severity") String severity, + @JsonProperty("Description") String description, + @JsonProperty("FixVersion") String fixVersion) { + this.id = id; + this.severity = severity; + this.description = description; + this.fixVersion = fixVersion; + } +} From fa9f1b52f77b898034dd604dde8021bcd53549f0 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:37:57 +0530 Subject: [PATCH 07/37] Refactoring package name and adding test for oss and mcp flag --- .../ContainersRealtimeImage.java | 4 +- .../ContainersRealtimeResults.java | 3 +- .../ContainersRealtimeVulnerability.java | 4 +- .../IacRealtimeResults.java | 4 +- .../OssRealtimeResults.java | 10 +- .../OssRealtimeScanPackage.java | 24 +-- .../OssRealtimeVulnerability.java | 4 +- .../SecretsRealtimeResults.java | 15 +- .../com/checkmarx/ast/wrapper/CxWrapper.java | 14 +- src/test/java/com/checkmarx/ast/ScanTest.java | 15 ++ .../java/com/checkmarx/ast/TenantTest.java | 8 +- .../ast/unit/OssRealtimeParsingTest.java | 165 ++++++++++++++++++ src/test/resources/ignored-packages.json | 1 + 13 files changed, 226 insertions(+), 45 deletions(-) rename src/main/java/com/checkmarx/ast/{containersRealtime => containersrealtime}/ContainersRealtimeImage.java (97%) rename src/main/java/com/checkmarx/ast/{containersRealtime => containersrealtime}/ContainersRealtimeResults.java (97%) rename src/main/java/com/checkmarx/ast/{containersRealtime => containersrealtime}/ContainersRealtimeVulnerability.java (94%) rename src/main/java/com/checkmarx/ast/{iacRealtime => iacrealtime}/IacRealtimeResults.java (98%) rename src/main/java/com/checkmarx/ast/{ossRealtime => ossrealtime}/OssRealtimeResults.java (87%) rename src/main/java/com/checkmarx/ast/{ossRealtime => ossrealtime}/OssRealtimeScanPackage.java (76%) rename src/main/java/com/checkmarx/ast/{ossRealtime => ossrealtime}/OssRealtimeVulnerability.java (96%) rename src/main/java/com/checkmarx/ast/{secretsRealtime => secretsrealtime}/SecretsRealtimeResults.java (87%) create mode 100644 src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java create mode 100644 src/test/resources/ignored-packages.json diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeImage.java similarity index 97% rename from src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java rename to src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeImage.java index 54789e49..7a6a33a6 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java +++ b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeImage.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.containersRealtime; +package com.checkmarx.ast.containersrealtime; import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -37,4 +37,4 @@ public ContainersRealtimeImage(@JsonProperty("ImageName") String imageName, this.status = status; this.vulnerabilities = vulnerabilities == null ? Collections.emptyList() : vulnerabilities; } -} +} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeResults.java similarity index 97% rename from src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java rename to src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeResults.java index f40747cd..f9054ffe 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.containersRealtime; +package com.checkmarx.ast.containersrealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -51,4 +51,3 @@ private static boolean isValidJSON(String json) { } } } - diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeVulnerability.java similarity index 94% rename from src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java rename to src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeVulnerability.java index e1458735..cabe4b68 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeVulnerability.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.containersRealtime; +package com.checkmarx.ast.containersrealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -21,4 +21,4 @@ public ContainersRealtimeVulnerability(@JsonProperty("CVE") String cve, this.cve = cve; this.severity = severity; } -} +} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java b/src/main/java/com/checkmarx/ast/iacrealtime/IacRealtimeResults.java similarity index 98% rename from src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java rename to src/main/java/com/checkmarx/ast/iacrealtime/IacRealtimeResults.java index df4dffa8..0afc103f 100644 --- a/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/iacrealtime/IacRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.iacRealtime; +package com.checkmarx.ast.iacrealtime; import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -95,4 +95,4 @@ private static boolean isValidJSON(String json) { return false; } } -} +} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java similarity index 87% rename from src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java rename to src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java index 61a516c9..7141370f 100644 --- a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossRealtime; +package com.checkmarx.ast.ossrealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.Collections; import java.util.List; @Value @@ -20,12 +21,12 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeResults { private static final Logger log = LoggerFactory.getLogger(OssRealtimeResults.class); - @JsonProperty("Packages") - List packages; + + @JsonProperty("Packages") List packages; @JsonCreator public OssRealtimeResults(@JsonProperty("Packages") List packages) { - this.packages = packages; + this.packages = packages == null ? Collections.emptyList() : packages; } public static OssRealtimeResults fromLine(String line) { @@ -51,3 +52,4 @@ private static boolean isValidJSON(String json) { } } } + diff --git a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java similarity index 76% rename from src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java rename to src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java index d8a7149f..d4b32771 100644 --- a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossRealtime; +package com.checkmarx.ast.ossrealtime; import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -16,20 +16,13 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeScanPackage { - @JsonProperty("PackageManager") - String packageManager; - @JsonProperty("PackageName") - String packageName; - @JsonProperty("PackageVersion") - String packageVersion; - @JsonProperty("FilePath") - String filePath; - @JsonProperty("Locations") - List locations; - @JsonProperty("Status") - String status; - @JsonProperty("Vulnerabilities") - List vulnerabilities; + @JsonProperty("PackageManager") String packageManager; + @JsonProperty("PackageName") String packageName; + @JsonProperty("PackageVersion") String packageVersion; + @JsonProperty("FilePath") String filePath; + @JsonProperty("Locations") List locations; + @JsonProperty("Status") String status; + @JsonProperty("Vulnerabilities") List vulnerabilities; @JsonCreator public OssRealtimeScanPackage(@JsonProperty("PackageManager") String packageManager, @@ -48,3 +41,4 @@ public OssRealtimeScanPackage(@JsonProperty("PackageManager") String packageMana this.vulnerabilities = vulnerabilities == null ? Collections.emptyList() : vulnerabilities; } } + diff --git a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java similarity index 96% rename from src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java rename to src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java index 00a4618a..7ac9c574 100644 --- a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossRealtime; +package com.checkmarx.ast.ossrealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -11,7 +11,6 @@ @JsonDeserialize @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) - public class OssRealtimeVulnerability { @JsonProperty("Id") String id; @JsonProperty("Severity") String severity; @@ -29,3 +28,4 @@ public OssRealtimeVulnerability(@JsonProperty("Id") String id, this.fixVersion = fixVersion; } } + diff --git a/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java b/src/main/java/com/checkmarx/ast/secretsrealtime/SecretsRealtimeResults.java similarity index 87% rename from src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java rename to src/main/java/com/checkmarx/ast/secretsrealtime/SecretsRealtimeResults.java index d04d00db..ef19050c 100644 --- a/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/secretsrealtime/SecretsRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.secretsRealtime; +package com.checkmarx.ast.secretsrealtime; import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -23,8 +23,7 @@ public class SecretsRealtimeResults { private static final Logger log = LoggerFactory.getLogger(SecretsRealtimeResults.class); - @JsonProperty("Secrets") - List secrets; // Normalized list (array or single object from CLI) + @JsonProperty("Secrets") List secrets; @JsonCreator public SecretsRealtimeResults(@JsonProperty("Secrets") List secrets) { @@ -61,7 +60,7 @@ public Secret(@JsonProperty("Title") String title, public static SecretsRealtimeResults fromLine(String line) { if (StringUtils.isBlank(line)) { - return null; // skip blank + return null; } try { if (!isValidJSON(line)) { @@ -69,12 +68,11 @@ public static SecretsRealtimeResults fromLine(String line) { } ObjectMapper mapper = new ObjectMapper(); String trimmed = line.trim(); - if (trimmed.startsWith("[")) { // array form - List list = mapper.readValue(trimmed, - mapper.getTypeFactory().constructCollectionType(List.class, Secret.class)); + if (trimmed.startsWith("[")) { + List list = mapper.readValue(trimmed, mapper.getTypeFactory().constructCollectionType(List.class, Secret.class)); return new SecretsRealtimeResults(list); } - if (trimmed.startsWith("{")) { // single object form + if (trimmed.startsWith("{")) { Secret single = mapper.readValue(trimmed, Secret.class); return new SecretsRealtimeResults(Collections.singletonList(single)); } @@ -93,3 +91,4 @@ private static boolean isValidJSON(String json) { } } } + diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index a8662bb9..85be850d 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -4,10 +4,10 @@ import com.checkmarx.ast.codebashing.CodeBashing; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; import com.checkmarx.ast.learnMore.LearnMore; -import com.checkmarx.ast.ossRealtime.OssRealtimeResults; -import com.checkmarx.ast.secretsRealtime.SecretsRealtimeResults; -import com.checkmarx.ast.iacRealtime.IacRealtimeResults; -import com.checkmarx.ast.containersRealtime.ContainersRealtimeResults; +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; +import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; +import com.checkmarx.ast.iacrealtime.IacRealtimeResults; +import com.checkmarx.ast.containersrealtime.ContainersRealtimeResults; import com.checkmarx.ast.predicate.CustomState; import com.checkmarx.ast.predicate.Predicate; import com.checkmarx.ast.project.Project; @@ -399,7 +399,7 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String arguments.add(fileSources); arguments.add(CxConstants.ADDITONAL_PARAMS); arguments.add(additionalParams); - if (engine.length() > 0) { + if (!engine.isEmpty()) { arguments.add(CxConstants.ENGINE); arguments.add(engine); } @@ -461,11 +461,11 @@ public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFil arguments.add(resultsFile); arguments.add(CxConstants.KICS_REMEDIATION_KICS_FILE); arguments.add(kicsFile); - if (engine.length() > 0) { + if (!engine.isEmpty()) { arguments.add(CxConstants.ENGINE); arguments.add(engine); } - if (similarityIds.length() > 0) { + if (!similarityIds.isEmpty()) { arguments.add(CxConstants.KICS_REMEDIATION_SIMILARITY); arguments.add(similarityIds); } diff --git a/src/test/java/com/checkmarx/ast/ScanTest.java b/src/test/java/com/checkmarx/ast/ScanTest.java index fb0b3b3b..5e31b337 100644 --- a/src/test/java/com/checkmarx/ast/ScanTest.java +++ b/src/test/java/com/checkmarx/ast/ScanTest.java @@ -3,7 +3,9 @@ import com.checkmarx.ast.asca.ScanDetail; import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; import com.checkmarx.ast.scan.Scan; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -92,4 +94,17 @@ void testKicsRealtimeScan() throws Exception { Assertions.assertTrue(scan.getResults().size() >= 1); } + @Test + void testOssRealtimeScanWithIgnoredFile() throws Exception { + Assumptions.assumeTrue(getConfig().getPathToExecutable() != null && !getConfig().getPathToExecutable().isEmpty(), "PATH_TO_EXECUTABLE not set"); + + String source = "pom.xml"; + String ignoreFile = "src/test/resources/ignored-packages.json"; + + OssRealtimeResults results = wrapper.ossRealtimeScan(source, ignoreFile); + + Assertions.assertNotNull(results); + Assertions.assertNotNull(results.getPackages()); + } + } diff --git a/src/test/java/com/checkmarx/ast/TenantTest.java b/src/test/java/com/checkmarx/ast/TenantTest.java index b9ac752c..7f49da16 100644 --- a/src/test/java/com/checkmarx/ast/TenantTest.java +++ b/src/test/java/com/checkmarx/ast/TenantTest.java @@ -11,11 +11,17 @@ public class TenantTest extends BaseTest { @Test void testTenantSettings() throws Exception { List tenantSettings = wrapper.tenantSettings(); - Assertions.assertTrue(tenantSettings.size() > 0); + Assertions.assertFalse(tenantSettings.isEmpty()); } @Test void testIdeScansEnabled() { Assertions.assertDoesNotThrow(() -> wrapper.ideScansEnabled()); } + + @Test + void testAiMcpServerEnabled() throws Exception { + boolean enabled = Assertions.assertDoesNotThrow(() -> wrapper.aiMcpServerEnabled()); + Assertions.assertTrue(enabled, "AI MCP Server flag expected to be true"); + } } diff --git a/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java b/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java new file mode 100644 index 00000000..f2ab9b4c --- /dev/null +++ b/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java @@ -0,0 +1,165 @@ +package com.checkmarx.ast.unit; + +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; +import com.checkmarx.ast.ossrealtime.OssRealtimeScanPackage; +import com.checkmarx.ast.ossrealtime.OssRealtimeVulnerability; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +/** + * Unit tests for OssRealtimeResults JSON parsing and object construction. + * Focus: JSON parsing branches, constructor defaults, null/empty handling. + */ +class OssRealtimeParsingTest { + + /** Packages value is a string -> parser should fail and return null. */ + @Test + void fromLine_PackagesStringType_ReturnsNull() { + String json = "{\"Packages\":\"oops\"}"; + Assertions.assertNull(OssRealtimeResults.fromLine(json)); + } + + /** Packages array contains non-object entries -> parsing should fail and return null. */ + @Test + void fromLine_PackagesArrayWithNonObjectEntries_ReturnsNull() { + String json = "{\"Packages\":[123,\"abc\"]}"; + Assertions.assertNull(OssRealtimeResults.fromLine(json)); + } + + /** Packages key absent entirely -> fromLine returns null. */ + @Test + void fromLine_LineDoesNotContainPackagesKey_ReturnsNull() { + String jsonLine = "{\"PackageManager\":\"npm\"}"; // No top-level "Packages" key + Assertions.assertNull(OssRealtimeResults.fromLine(jsonLine)); + } + + /** Truncated JSON containing Packages key -> parse exception caught, returns null. */ + @Test + void fromLine_TruncatedJsonWithPackagesKey_CatchesAndReturnsNull() { + String truncated = "{\"Packages\":[{"; // contains "Packages" but invalid JSON + Assertions.assertNull(OssRealtimeResults.fromLine(truncated)); + } + + /** Packages key present but explicitly null -> results object with empty list. */ + @Test + void fromLine_PackagesNull_YieldsEmptyList() { + String json = "{\"Packages\":null}"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + Assertions.assertTrue(results.getPackages().isEmpty()); + } + + /** Empty Packages array -> empty list returned. */ + @Test + void fromLine_EmptyPackagesArray() { + String json = "{\"Packages\":[]}"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + Assertions.assertTrue(results.getPackages().isEmpty()); + } + + /** Package missing optional fields: packageManager & packageVersion should map to null. */ + @Test + void parsePackageMissingFields_AllowsNulls() { + String json = "{ \"Packages\": [{ \"PackageName\": \"only-name\", \"FilePath\": \"package.json\", \"Status\": \"OK\" }] }"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getPackages().size()); + OssRealtimeScanPackage pkg = results.getPackages().get(0); + Assertions.assertNull(pkg.getPackageManager()); + Assertions.assertEquals("only-name", pkg.getPackageName()); + Assertions.assertNull(pkg.getPackageVersion()); + } + + /** Unicode characters preserved in Description field. */ + @Test + void parseUnicodeInDescription() { + String json = "{ \"Packages\": [{ \"PackageManager\": \"npm\", \"PackageName\": \"u\", \"PackageVersion\": \"1\", \"FilePath\": \"p.json\", \"Status\": \"OK\", \"Vulnerabilities\": [{ \"Id\": \"CVE-u\", \"Severity\": \"Low\", \"Description\": \"Unicode snow ☃ and emoji 🚀\" }] }] }"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getPackages().size()); + OssRealtimeVulnerability vul = results.getPackages().get(0).getVulnerabilities().get(0); + Assertions.assertEquals("Unicode snow ☃ and emoji 🚀", vul.getDescription()); + Assertions.assertEquals("CVE-u", vul.getId()); + } + + /** Multiple vulnerabilities: one without fixVersion, one with fixVersion. */ + @Test + void parseMultipleVulnerabilities() { + String json = "{\n" + + " \"Packages\": [{\n" + + " \"PackageManager\": \"npm\",\n" + + " \"PackageName\": \"dep\",\n" + + " \"PackageVersion\": \"1.0.0\",\n" + + " \"FilePath\": \"/a/package.json\",\n" + + " \"Status\": \"OK\",\n" + + " \"Vulnerabilities\": [\n" + + " { \"Id\": \"CVE-1\", \"Severity\": \"Low\", \"Description\": \"d1\" },\n" + + " { \"Id\": \"CVE-2\", \"Severity\": \"Critical\", \"Description\": \"d2\", \"FixVersion\": \"2.0.0\" }\n" + + " ]\n" + + " }]\n" + + "}"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + OssRealtimeScanPackage pkg = results.getPackages().get(0); + List vulns = pkg.getVulnerabilities(); + Assertions.assertEquals(2, vulns.size()); + Assertions.assertEquals("CVE-1", vulns.get(0).getId()); + Assertions.assertNull(vulns.get(0).getFixVersion()); + Assertions.assertEquals("CVE-2", vulns.get(1).getId()); + Assertions.assertEquals("2.0.0", vulns.get(1).getFixVersion()); + } + + /** Explicit null lists should be normalized to empty lists by constructor. */ + @Test + void constructor_DefaultsEmptyListsWhenNull() throws IOException { + String json = "{\n" + + " \"Packages\": [{\n" + + " \"PackageManager\": \"pip\",\n" + + " \"PackageName\": \"requests\",\n" + + " \"PackageVersion\": \"2.0.0\",\n" + + " \"FilePath\": \"requirements.txt\",\n" + + " \"Status\": \"Unknown\",\n" + + " \"Locations\": null,\n" + + " \"Vulnerabilities\": null\n" + + " }]\n" + + "}"; + OssRealtimeResults results = new ObjectMapper().readValue(json, OssRealtimeResults.class); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getPackages().size()); + OssRealtimeScanPackage pkg = results.getPackages().get(0); + Assertions.assertTrue(pkg.getLocations().isEmpty()); + Assertions.assertTrue(pkg.getVulnerabilities().isEmpty()); + } + + /** All vulnerability fields mapped including fixVersion. */ + @Test + void vulnerability_AllFieldsMapped() { + String json = "{\n" + + " \"Packages\": [{\n" + + " \"PackageManager\": \"npm\",\n" + + " \"PackageName\": \"chalk\",\n" + + " \"PackageVersion\": \"5.0.0\",\n" + + " \"FilePath\": \"/w/package.json\",\n" + + " \"Status\": \"OK\",\n" + + " \"Vulnerabilities\": [{\n" + + " \"Id\": \"CVE-2025-9999\",\n" + + " \"Severity\": \"Medium\",\n" + + " \"Description\": \"Some issue\",\n" + + " \"FixVersion\": \"5.0.1\"\n" + + " }]\n" + + " }]\n" + + "}"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + OssRealtimeVulnerability vul = results.getPackages().get(0).getVulnerabilities().get(0); + Assertions.assertEquals("CVE-2025-9999", vul.getId()); + Assertions.assertEquals("Medium", vul.getSeverity()); + Assertions.assertEquals("Some issue", vul.getDescription()); + Assertions.assertEquals("5.0.1", vul.getFixVersion()); + } +} diff --git a/src/test/resources/ignored-packages.json b/src/test/resources/ignored-packages.json new file mode 100644 index 00000000..2ebbedc2 --- /dev/null +++ b/src/test/resources/ignored-packages.json @@ -0,0 +1 @@ +[{"name": "marked", "version": "*"}] \ No newline at end of file From ece0319e2d7ce57479a72e7a5cfef404477a4512 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:29:59 +0530 Subject: [PATCH 08/37] Add integration tests for OSS, Container, and Secrets realtime scanners --- .../ast/ContainersRealtimeResultsTest.java | 241 +++++++++++++++ .../checkmarx/ast/IacRealtimeResultsTest.java | 60 ++++ .../checkmarx/ast/OssRealtimeParsingTest.java | 157 ++++++++++ .../ast/SecretsRealtimeResultsTest.java | 292 ++++++++++++++++++ .../ast/unit/OssRealtimeParsingTest.java | 165 ---------- src/test/resources/ignored-packages.json | 6 +- 6 files changed, 755 insertions(+), 166 deletions(-) create mode 100644 src/test/java/com/checkmarx/ast/ContainersRealtimeResultsTest.java create mode 100644 src/test/java/com/checkmarx/ast/IacRealtimeResultsTest.java create mode 100644 src/test/java/com/checkmarx/ast/OssRealtimeParsingTest.java create mode 100644 src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java delete mode 100644 src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java diff --git a/src/test/java/com/checkmarx/ast/ContainersRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/ContainersRealtimeResultsTest.java new file mode 100644 index 00000000..6911e4af --- /dev/null +++ b/src/test/java/com/checkmarx/ast/ContainersRealtimeResultsTest.java @@ -0,0 +1,241 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.containersrealtime.ContainersRealtimeImage; +import com.checkmarx.ast.containersrealtime.ContainersRealtimeResults; +import com.checkmarx.ast.containersrealtime.ContainersRealtimeVulnerability; +import com.checkmarx.ast.wrapper.CxException; +import org.junit.jupiter.api.*; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration and unit tests for Container Realtime scanner functionality. + * Tests the complete workflow: CLI invocation -> JSON parsing -> domain object mapping. + * Integration tests use Dockerfile as the scan target and are assumption-guarded for CI/local flexibility. + */ +class ContainersRealtimeResultsTest extends BaseTest { + + private boolean isCliConfigured() { + return Optional.ofNullable(getConfig().getPathToExecutable()).filter(s -> !s.isEmpty()).isPresent(); + } + + /* ------------------------------------------------------ */ + /* Integration tests for Container Realtime scanning */ + /* ------------------------------------------------------ */ + + /** + * Tests basic container realtime scan functionality on Dockerfile. + * Verifies that the scan returns a valid results object with detected container images. + * This test validates the end-to-end workflow from CLI execution to domain object creation. + */ + @Test + @DisplayName("Basic container scan on Dockerfile returns detected images") + void basicContainerRealtimeScan() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String dockerfilePath = "src/test/resources/Dockerfile"; + Assumptions.assumeTrue(Files.exists(Paths.get(dockerfilePath)), "Dockerfile not found - cannot test container scanning"); + + ContainersRealtimeResults results = wrapper.containersRealtimeScan(dockerfilePath, ""); + + assertNotNull(results, "Scan should return non-null results"); + assertNotNull(results.getImages(), "Images list should be initialized"); + + // Verify that if images are detected, they have proper structure + if (!results.getImages().isEmpty()) { + results.getImages().forEach(image -> { + assertNotNull(image.getImageName(), "Image name should be populated"); + assertNotNull(image.getVulnerabilities(), "Vulnerabilities list should be initialized"); + }); + } + } + + /** + * Tests container scan with ignore file functionality. + * Verifies that providing an ignore file doesn't break the scanning process + * and produces consistent or reduced results compared to baseline scan. + */ + @Test + @DisplayName("Container scan with ignore file works correctly") + void containerRealtimeScanWithIgnoreFile() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String dockerfilePath = "src/test/resources/Dockerfile"; + String ignoreFile = "src/test/resources/ignored-packages.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(dockerfilePath)) && Files.exists(Paths.get(ignoreFile)), + "Required test resources missing - cannot test ignore functionality"); + + ContainersRealtimeResults baseline = wrapper.containersRealtimeScan(dockerfilePath, ""); + ContainersRealtimeResults filtered = wrapper.containersRealtimeScan(dockerfilePath, ignoreFile); + + assertNotNull(baseline, "Baseline scan should return results"); + assertNotNull(filtered, "Filtered scan should return results"); + + // Ignore file should not increase the number of detected issues + if (baseline.getImages() != null && filtered.getImages() != null) { + assertTrue(filtered.getImages().size() <= baseline.getImages().size(), + "Filtered scan should not have more images than baseline"); + } + } + + /** + * Tests scan consistency by running the same container scan multiple times. + * Verifies that repeated scans of the same Dockerfile produce stable, deterministic results. + * This is important for CI/CD pipelines where consistent results are crucial. + */ + @Test + @DisplayName("Repeated container scans produce consistent results") + void containerRealtimeScanConsistency() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String dockerfilePath = "src/test/resources/Dockerfile"; + Assumptions.assumeTrue(Files.exists(Paths.get(dockerfilePath)), "Dockerfile not found - cannot test consistency"); + + ContainersRealtimeResults firstScan = wrapper.containersRealtimeScan(dockerfilePath, ""); + ContainersRealtimeResults secondScan = wrapper.containersRealtimeScan(dockerfilePath, ""); + + assertNotNull(firstScan, "First scan should return results"); + assertNotNull(secondScan, "Second scan should return results"); + + // Compare image counts for consistency + int firstImageCount = (firstScan.getImages() != null) ? firstScan.getImages().size() : 0; + int secondImageCount = (secondScan.getImages() != null) ? secondScan.getImages().size() : 0; + + assertEquals(firstImageCount, secondImageCount, + "Image count should be consistent across multiple scans"); + } + + /** + * Tests domain object mapping for container scan results. + * Verifies that JSON responses are properly parsed into domain objects + * and all expected fields are correctly mapped and initialized. + */ + @Test + @DisplayName("Container domain objects are properly mapped from scan results") + void containerDomainObjectMapping() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String dockerfilePath = "src/test/resources/Dockerfile"; + Assumptions.assumeTrue(Files.exists(Paths.get(dockerfilePath)), "Dockerfile not found - cannot test mapping"); + + ContainersRealtimeResults results = wrapper.containersRealtimeScan(dockerfilePath, ""); + assertNotNull(results, "Scan results should not be null"); + + // If images are detected, validate their structure + if (results.getImages() != null && !results.getImages().isEmpty()) { + ContainersRealtimeImage sampleImage = results.getImages().get(0); + + // Verify core image fields are mapped correctly + assertNotNull(sampleImage.getImageName(), "Image name should always be present"); + assertNotNull(sampleImage.getVulnerabilities(), "Vulnerabilities list should be initialized"); + + // If vulnerabilities exist, validate their structure + if (!sampleImage.getVulnerabilities().isEmpty()) { + ContainersRealtimeVulnerability sampleVuln = sampleImage.getVulnerabilities().get(0); + // CVE and Severity are the core fields that should be present + assertTrue(sampleVuln.getCve() != null || sampleVuln.getSeverity() != null, + "Vulnerability should have at least CVE or Severity information"); + } + } + } + + /** + * Tests error handling when scanning a non-existent file. + * Verifies that the scanner properly throws a CxException with meaningful error message + * when provided with invalid file paths, demonstrating proper error handling. + */ + @Test + @DisplayName("Container scan throws appropriate exception for non-existent file") + void containerScanHandlesInvalidPath() { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + // Test with a non-existent file path + String invalidPath = "src/test/resources/NonExistentDockerfile"; + + // The CLI should throw a CxException with a meaningful error message for invalid paths + CxException exception = assertThrows(CxException.class, () -> + wrapper.containersRealtimeScan(invalidPath, "") + ); + + // Verify the exception contains information about the invalid file path + String errorMessage = exception.getMessage(); + assertNotNull(errorMessage, "Exception should contain an error message"); + assertTrue(errorMessage.contains("invalid file path") || errorMessage.contains("file") || errorMessage.contains("path"), + "Exception message should indicate the issue is related to file path: " + errorMessage); + } + + /* ------------------------------------------------------ */ + /* Unit tests for JSON parsing robustness */ + /* ------------------------------------------------------ */ + + /** + * Tests JSON parsing with valid container scan response. + * Verifies that well-formed JSON is correctly parsed into domain objects. + */ + @Test + @DisplayName("Valid JSON parsing creates correct domain objects") + void testFromLineWithValidJson() { + String json = "{" + + "\"Images\": [" + + " {" + + " \"ImageName\": \"nginx:latest\"," + + " \"Vulnerabilities\": [" + + " {" + + " \"CVE\": \"CVE-2021-2345\"," + + " \"Severity\": \"High\"" + + " }" + + " ]" + + " }" + + "]" + + "}"; + ContainersRealtimeResults results = ContainersRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getImages().size()); + ContainersRealtimeImage image = results.getImages().get(0); + assertEquals("nginx:latest", image.getImageName()); + assertEquals(1, image.getVulnerabilities().size()); + ContainersRealtimeVulnerability vulnerability = image.getVulnerabilities().get(0); + assertEquals("CVE-2021-2345", vulnerability.getCve()); + assertEquals("High", vulnerability.getSeverity()); + } + + /** + * Tests parsing robustness with malformed JSON. + * Verifies that the parser gracefully handles various edge cases. + */ + @Test + @DisplayName("Malformed JSON is handled gracefully") + void testFromLineWithEdgeCases() { + // Missing Images key + assertNull(ContainersRealtimeResults.fromLine("{\"some_other_key\": \"some_value\"}")); + + // Invalid JSON structure + assertNull(ContainersRealtimeResults.fromLine("{\"Images\": [}")); + + // Blank/null inputs + assertNull(ContainersRealtimeResults.fromLine("")); + assertNull(ContainersRealtimeResults.fromLine(" ")); + assertNull(ContainersRealtimeResults.fromLine(null)); + } + + /** + * Tests parsing with empty or null image arrays. + * Verifies that empty results are handled correctly. + */ + @Test + @DisplayName("Empty and null image arrays are handled correctly") + void testFromLineWithEmptyResults() { + // Empty images array + String emptyJson = "{\"Images\": []}"; + ContainersRealtimeResults emptyResults = ContainersRealtimeResults.fromLine(emptyJson); + assertNotNull(emptyResults); + assertTrue(emptyResults.getImages().isEmpty()); + + // Null images + String nullJson = "{\"Images\": null}"; + ContainersRealtimeResults nullResults = ContainersRealtimeResults.fromLine(nullJson); + assertNotNull(nullResults); + assertNull(nullResults.getImages()); + } +} + diff --git a/src/test/java/com/checkmarx/ast/IacRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/IacRealtimeResultsTest.java new file mode 100644 index 00000000..ce7c8b7f --- /dev/null +++ b/src/test/java/com/checkmarx/ast/IacRealtimeResultsTest.java @@ -0,0 +1,60 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.iacrealtime.IacRealtimeResults; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class IacRealtimeResultsTest { + + @Test + void testFromLineWithValidJsonArray() { + String json = "[" + + " {" + + " \"Title\": \"My Issue\"," + + " \"Severity\": \"High\"" + + " }" + + "]"; + IacRealtimeResults results = IacRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getResults().size()); + IacRealtimeResults.Issue issue = results.getResults().get(0); + assertEquals("My Issue", issue.getTitle()); + assertEquals("High", issue.getSeverity()); + } + + @Test + void testFromLineWithValidJsonObject() { + String json = "{" + + " \"Title\": \"My Single Issue\"," + + " \"Severity\": \"Medium\"" + + "}"; + IacRealtimeResults results = IacRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getResults().size()); + IacRealtimeResults.Issue issue = results.getResults().get(0); + assertEquals("My Single Issue", issue.getTitle()); + assertEquals("Medium", issue.getSeverity()); + } + + @Test + void testFromLineWithEmptyJsonArray() { + String json = "[]"; + IacRealtimeResults results = IacRealtimeResults.fromLine(json); + assertNotNull(results); + assertTrue(results.getResults().isEmpty()); + } + + @Test + void testFromLineWithBlankLine() { + assertNull(IacRealtimeResults.fromLine("")); + assertNull(IacRealtimeResults.fromLine(" ")); + assertNull(IacRealtimeResults.fromLine(null)); + } + + @Test + void testFromLineWithInvalidJson() { + String json = "[{]"; + assertNull(IacRealtimeResults.fromLine(json)); + } +} + diff --git a/src/test/java/com/checkmarx/ast/OssRealtimeParsingTest.java b/src/test/java/com/checkmarx/ast/OssRealtimeParsingTest.java new file mode 100644 index 00000000..6ea0fea0 --- /dev/null +++ b/src/test/java/com/checkmarx/ast/OssRealtimeParsingTest.java @@ -0,0 +1,157 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; +import com.checkmarx.ast.ossrealtime.OssRealtimeScanPackage; +import org.junit.jupiter.api.*; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests for OSS Realtime scanner functionality. + * Tests the complete workflow: CLI invocation -> JSON parsing -> domain object mapping. + * All tests use pom.xml as the scan target and are assumption-guarded for CI/local flexibility. + */ +class OssRealtimeParsingTest extends BaseTest { + + private boolean isCliConfigured() { + return Optional.ofNullable(getConfig().getPathToExecutable()).filter(s -> !s.isEmpty()).isPresent(); + } + + /** + * Tests basic OSS realtime scan functionality on pom.xml. + * Verifies that the scan returns a valid results object with detected Maven dependencies. + */ + @Test + @DisplayName("Basic OSS scan on pom.xml returns Maven dependencies") + void basicOssRealtimeScan() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + OssRealtimeResults results = wrapper.ossRealtimeScan("pom.xml", ""); + + assertNotNull(results, "Scan should return non-null results"); + assertFalse(results.getPackages().isEmpty(), "Should detect Maven dependencies in pom.xml"); + + // Verify each package has required fields populated + results.getPackages().forEach(pkg -> { + assertNotNull(pkg.getPackageName(), "Package name should be populated"); + assertNotNull(pkg.getStatus(), "Package status should be populated"); + }); + } + + /** + * Tests OSS scan with ignore file functionality. + * Verifies that providing an ignore file reduces or maintains the package count compared to baseline scan. + */ + @Test + @DisplayName("OSS scan with ignore file filters packages correctly") + void ossRealtimeScanWithIgnoreFile() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String ignoreFile = "src/test/resources/ignored-packages.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(ignoreFile)), "Ignore file not found - cannot test ignore functionality"); + + OssRealtimeResults baseline = wrapper.ossRealtimeScan("pom.xml", ""); + OssRealtimeResults filtered = wrapper.ossRealtimeScan("pom.xml", ignoreFile); + + assertNotNull(baseline, "Baseline scan should return results"); + assertNotNull(filtered, "Filtered scan should return results"); + assertTrue(filtered.getPackages().size() <= baseline.getPackages().size(), + "Filtered scan should have same or fewer packages than baseline"); + } + + /** + * Diagnostic test to see what package names are actually detected by the OSS scanner. + * This helps identify the correct package names for ignore file testing. + */ + @Test + @DisplayName("Display detected package names for diagnostic purposes") + void diagnosticPackageNames() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + OssRealtimeResults results = wrapper.ossRealtimeScan("pom.xml", ""); + assertFalse(results.getPackages().isEmpty(), "Should have packages for diagnostic"); + + // Print package names for debugging (will show in test output) + System.out.println("Detected package names:"); + results.getPackages().forEach(pkg -> + System.out.println(" - " + pkg.getPackageName() + " (Manager: " + pkg.getPackageManager() + ")") + ); + + // This test always passes - it's just for information gathering + assertTrue(true, "Diagnostic test completed"); + } + + /** + * Tests that specific packages listed in ignore file are actually excluded from scan results. + * Uses a more flexible approach to find packages that can be ignored. + */ + @Test + @DisplayName("Ignore file excludes detected packages correctly") + void ignoreFileExcludesPackages() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String ignoreFile = "src/test/resources/ignored-packages.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(ignoreFile)), "Ignore file not found - cannot test ignore functionality"); + + OssRealtimeResults baseline = wrapper.ossRealtimeScan("pom.xml", ""); + OssRealtimeResults filtered = wrapper.ossRealtimeScan("pom.xml", ignoreFile); + + // Look for common Maven packages that might be detected + String[] commonPackageNames = {"jackson-databind", "commons-lang3", "json-simple", "slf4j-simple", "junit-jupiter"}; + + boolean foundIgnoredPackage = false; + for (String packageName : commonPackageNames) { + boolean inBaseline = baseline.getPackages().stream() + .anyMatch(pkg -> packageName.equalsIgnoreCase(pkg.getPackageName())); + boolean inFiltered = filtered.getPackages().stream() + .anyMatch(pkg -> packageName.equalsIgnoreCase(pkg.getPackageName())); + + if (inBaseline && !inFiltered) { + foundIgnoredPackage = true; + System.out.println("Successfully filtered out package: " + packageName); + break; + } + } + assertTrue(filtered.getPackages().size() <= baseline.getPackages().size(), + "Filtered scan should not have more packages than baseline"); + } + + /** + * Tests scan consistency by running the same scan multiple times. + * Verifies that repeated scans of the same source produce stable, deterministic results. + */ + @Test + @DisplayName("Repeated OSS scans produce consistent results") + void ossRealtimeScanConsistency() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + OssRealtimeResults firstScan = wrapper.ossRealtimeScan("pom.xml", ""); + OssRealtimeResults secondScan = wrapper.ossRealtimeScan("pom.xml", ""); + + assertEquals(firstScan.getPackages().size(), secondScan.getPackages().size(), + "Package count should be consistent across multiple scans"); + } + + /** + * Tests domain object mapping by verifying all expected package fields are properly populated. + * Ensures the JSON to POJO conversion works correctly for all package attributes. + */ + @Test + @DisplayName("Package domain objects are properly mapped from scan results") + void packageDomainObjectMapping() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + OssRealtimeResults results = wrapper.ossRealtimeScan("pom.xml", ""); + assertFalse(results.getPackages().isEmpty(), "Should have packages to validate mapping"); + + OssRealtimeScanPackage samplePackage = results.getPackages().get(0); + + // Verify core package fields are mapped (some may be null based on scan results) + assertNotNull(samplePackage.getPackageName(), "Package name should always be present"); + assertNotNull(samplePackage.getStatus(), "Package status should always be present"); + assertNotNull(samplePackage.getLocations(), "Locations list should be initialized (may be empty)"); + assertNotNull(samplePackage.getVulnerabilities(), "Vulnerabilities list should be initialized (may be empty)"); + } +} diff --git a/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java new file mode 100644 index 00000000..a60680fe --- /dev/null +++ b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java @@ -0,0 +1,292 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.realtime.RealtimeLocation; +import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; +import com.checkmarx.ast.wrapper.CxException; +import org.junit.jupiter.api.*; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration and unit tests for Secrets Realtime scanner functionality. + * Tests the complete workflow: CLI invocation -> JSON parsing -> domain object mapping. + * Integration tests use python-vul-file.py as the scan target and are assumption-guarded for CI/local flexibility. + */ +class SecretsRealtimeResultsTest extends BaseTest { + + private boolean isCliConfigured() { + return Optional.ofNullable(getConfig().getPathToExecutable()).filter(s -> !s.isEmpty()).isPresent(); + } + + /* ------------------------------------------------------ */ + /* Integration tests for Secrets Realtime scanning */ + /* ------------------------------------------------------ */ + + /** + * Tests basic secrets realtime scan functionality on a vulnerable Python file. + * Verifies that the scan returns a valid results object and can detect hardcoded secrets + * such as passwords and credentials embedded in the source code. + */ + @Test + @DisplayName("Basic secrets scan on python file returns detected secrets") + void basicSecretsRealtimeScan() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String pythonFile = "src/test/resources/python-vul-file.py"; + Assumptions.assumeTrue(Files.exists(Paths.get(pythonFile)), "Python vulnerable file not found - cannot test secrets scanning"); + + SecretsRealtimeResults results = wrapper.secretsRealtimeScan(pythonFile, ""); + + assertNotNull(results, "Scan should return non-null results"); + assertNotNull(results.getSecrets(), "Secrets list should be initialized"); + + // The python file contains hardcoded credentials, so we expect some secrets to be found + if (!results.getSecrets().isEmpty()) { + results.getSecrets().forEach(secret -> { + assertNotNull(secret.getTitle(), "Secret title should be populated"); + assertNotNull(secret.getFilePath(), "Secret file path should be populated"); + assertNotNull(secret.getLocations(), "Secret locations should be initialized"); + }); + } + } + + /** + * Tests secrets scan with ignore file functionality. + * Verifies that providing an ignore file doesn't break the scanning process + * and produces consistent or reduced results compared to baseline scan. + */ + @Test + @DisplayName("Secrets scan with ignore file works correctly") + void secretsRealtimeScanWithIgnoreFile() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String pythonFile = "src/test/resources/python-vul-file.py"; + String ignoreFile = "src/test/resources/ignored-packages.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(pythonFile)) && Files.exists(Paths.get(ignoreFile)), + "Required test resources missing - cannot test ignore functionality"); + + SecretsRealtimeResults baseline = wrapper.secretsRealtimeScan(pythonFile, ""); + SecretsRealtimeResults filtered = wrapper.secretsRealtimeScan(pythonFile, ignoreFile); + + assertNotNull(baseline, "Baseline scan should return results"); + assertNotNull(filtered, "Filtered scan should return results"); + + // Ignore file should not increase the number of detected secrets + assertTrue(filtered.getSecrets().size() <= baseline.getSecrets().size(), + "Filtered scan should not have more secrets than baseline"); + } + + /** + * Tests scan consistency by running the same secrets scan multiple times. + * Verifies that repeated scans of the same file produce stable, deterministic results. + * This is crucial for ensuring reliable CI/CD pipeline integration. + */ + @Test + @DisplayName("Repeated secrets scans produce consistent results") + void secretsRealtimeScanConsistency() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String pythonFile = "src/test/resources/python-vul-file.py"; + Assumptions.assumeTrue(Files.exists(Paths.get(pythonFile)), "Python file not found - cannot test consistency"); + + SecretsRealtimeResults firstScan = wrapper.secretsRealtimeScan(pythonFile, ""); + SecretsRealtimeResults secondScan = wrapper.secretsRealtimeScan(pythonFile, ""); + + assertNotNull(firstScan, "First scan should return results"); + assertNotNull(secondScan, "Second scan should return results"); + + // Compare secret counts for consistency + assertEquals(firstScan.getSecrets().size(), secondScan.getSecrets().size(), + "Secret count should be consistent across multiple scans"); + } + + /** + * Tests domain object mapping for secrets scan results. + * Verifies that JSON responses are properly parsed into domain objects + * and all expected fields (title, description, severity, locations) are correctly mapped. + */ + @Test + @DisplayName("Secret domain objects are properly mapped from scan results") + void secretDomainObjectMapping() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String pythonFile = "src/test/resources/python-vul-file.py"; + Assumptions.assumeTrue(Files.exists(Paths.get(pythonFile)), "Python file not found - cannot test mapping"); + + SecretsRealtimeResults results = wrapper.secretsRealtimeScan(pythonFile, ""); + assertNotNull(results, "Scan results should not be null"); + + // If secrets are detected, validate their structure + if (!results.getSecrets().isEmpty()) { + SecretsRealtimeResults.Secret sampleSecret = results.getSecrets().get(0); + + // Verify core secret fields are mapped correctly + assertNotNull(sampleSecret.getTitle(), "Secret title should always be present"); + assertNotNull(sampleSecret.getFilePath(), "Secret file path should always be present"); + assertNotNull(sampleSecret.getLocations(), "Locations list should be initialized"); + + // Verify locations have proper structure if they exist + if (!sampleSecret.getLocations().isEmpty()) { + RealtimeLocation sampleLocation = sampleSecret.getLocations().get(0); + assertTrue(sampleLocation.getLine() > 0, "Line number should be positive"); + } + } + } + + /** + * Tests secrets scanning on a clean file that should not contain secrets. + * Verifies that the scanner correctly identifies files without secrets + * and returns empty results without errors. + */ + @Test + @DisplayName("Secrets scan on clean file returns empty results") + void secretsScanOnCleanFile() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String cleanFile = "src/test/resources/csharp-no-vul.cs"; + Assumptions.assumeTrue(Files.exists(Paths.get(cleanFile)), "Clean C# file not found - cannot test clean scan"); + + SecretsRealtimeResults results = wrapper.secretsRealtimeScan(cleanFile, ""); + assertNotNull(results, "Scan results should not be null even for clean files"); + + // Clean file should have no secrets or very few false positives + assertTrue(results.getSecrets().size() <= 2, + "Clean file should have no or minimal secrets detected"); + } + + /** + * Tests error handling when scanning a non-existent file. + * Verifies that the scanner properly throws a CxException with meaningful error message + * when provided with invalid file paths, demonstrating proper error handling. + */ + @Test + @DisplayName("Secrets scan throws appropriate exception for non-existent file") + void secretsScanHandlesInvalidPath() { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + // Test with a non-existent file path + String invalidPath = "src/test/resources/NonExistentFile.py"; + + // The CLI should throw a CxException with a meaningful error message for invalid paths + CxException exception = assertThrows(CxException.class, () -> + wrapper.secretsRealtimeScan(invalidPath, "") + ); + + // Verify the exception contains information about the invalid file path + String errorMessage = exception.getMessage(); + assertNotNull(errorMessage, "Exception should contain an error message"); + assertTrue(errorMessage.contains("invalid file path") || errorMessage.contains("file") || errorMessage.contains("path"), + "Exception message should indicate the issue is related to file path: " + errorMessage); + } + + /** + * Tests secrets scanning across multiple file types. + * Verifies that the scanner can handle different file extensions and formats + * without crashing and produces appropriate results for each file type. + */ + @Test + @DisplayName("Secrets scan handles multiple file types correctly") + void secretsScanMultipleFileTypes() { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + String[] testFiles = { + "src/test/resources/python-vul-file.py", + "src/test/resources/csharp-file.cs", + "src/test/resources/Dockerfile" + }; + + for (String filePath : testFiles) { + if (Files.exists(Paths.get(filePath))) { + assertDoesNotThrow(() -> { + SecretsRealtimeResults results = wrapper.secretsRealtimeScan(filePath, ""); + assertNotNull(results, "Results should not be null for file: " + filePath); + }, "Scanner should handle file type gracefully: " + filePath); + } + } + } + + /* ------------------------------------------------------ */ + /* Unit tests for JSON parsing robustness */ + /* ------------------------------------------------------ */ + + /** + * Tests JSON parsing with valid secrets scan response containing array format. + * Verifies that well-formed JSON arrays are correctly parsed into domain objects. + */ + @Test + @DisplayName("Valid JSON array parsing creates correct domain objects") + void testFromLineWithJsonArray() { + String json = "[" + + "{" + + "\"Title\":\"Hardcoded AWS Access Key\"," + + "\"Description\":\"An AWS access key is hardcoded in the source code. This is a security risk.\"," + + "\"SecretValue\":\"AKIAIOSFODNN7EXAMPLE\"," + + "\"FilePath\":\"/path/to/file.py\"," + + "\"Severity\":\"HIGH\"," + + "\"Locations\":[{\"StartLine\":10,\"StartColumn\":5,\"EndLine\":10,\"EndColumn\":25}]" + + "}" + + "]"; + SecretsRealtimeResults results = SecretsRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getSecrets().size()); + SecretsRealtimeResults.Secret secret = results.getSecrets().get(0); + assertEquals("Hardcoded AWS Access Key", secret.getTitle()); + assertEquals("An AWS access key is hardcoded in the source code. This is a security risk.", secret.getDescription()); + assertEquals("AKIAIOSFODNN7EXAMPLE", secret.getSecretValue()); + assertEquals("/path/to/file.py", secret.getFilePath()); + assertEquals("HIGH", secret.getSeverity()); + assertEquals(1, secret.getLocations().size()); + } + + /** + * Tests JSON parsing with valid secrets scan response containing single object format. + * Verifies that single JSON objects are correctly parsed into domain objects. + */ + @Test + @DisplayName("Valid JSON object parsing creates correct domain objects") + void testFromLineWithJsonObject() { + String json = "{" + + "\"Title\":\"Hardcoded AWS Access Key\"," + + "\"Description\":\"An AWS access key is hardcoded in the source code. This is a security risk.\"," + + "\"SecretValue\":\"AKIAIOSFODNN7EXAMPLE\"," + + "\"FilePath\":\"/path/to/file.py\"," + + "\"Severity\":\"HIGH\"," + + "\"Locations\":[{\"StartLine\":10,\"StartColumn\":5,\"EndLine\":10,\"EndColumn\":25}]" + + "}"; + SecretsRealtimeResults results = SecretsRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getSecrets().size()); + SecretsRealtimeResults.Secret secret = results.getSecrets().get(0); + assertEquals("Hardcoded AWS Access Key", secret.getTitle()); + } + + /** + * Tests parsing robustness with malformed JSON and edge cases. + * Verifies that the parser gracefully handles various invalid input scenarios. + */ + @Test + @DisplayName("Malformed JSON and edge cases are handled gracefully") + void testFromLineWithEdgeCases() { + // Blank/null inputs + assertNull(SecretsRealtimeResults.fromLine("")); + assertNull(SecretsRealtimeResults.fromLine(" ")); + assertNull(SecretsRealtimeResults.fromLine(null)); + + // Invalid JSON structures + assertNull(SecretsRealtimeResults.fromLine("{")); + assertNull(SecretsRealtimeResults.fromLine("not a json")); + } + + /** + * Tests parsing with empty results. + * Verifies that empty JSON arrays are handled correctly and produce valid empty results. + */ + @Test + @DisplayName("Empty JSON arrays are handled correctly") + void testFromLineWithEmptyResults() { + String emptyJson = "[]"; + SecretsRealtimeResults results = SecretsRealtimeResults.fromLine(emptyJson); + assertNotNull(results); + assertTrue(results.getSecrets().isEmpty()); + } +} + diff --git a/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java b/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java deleted file mode 100644 index f2ab9b4c..00000000 --- a/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.checkmarx.ast.unit; - -import com.checkmarx.ast.ossrealtime.OssRealtimeResults; -import com.checkmarx.ast.ossrealtime.OssRealtimeScanPackage; -import com.checkmarx.ast.ossrealtime.OssRealtimeVulnerability; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.List; - -/** - * Unit tests for OssRealtimeResults JSON parsing and object construction. - * Focus: JSON parsing branches, constructor defaults, null/empty handling. - */ -class OssRealtimeParsingTest { - - /** Packages value is a string -> parser should fail and return null. */ - @Test - void fromLine_PackagesStringType_ReturnsNull() { - String json = "{\"Packages\":\"oops\"}"; - Assertions.assertNull(OssRealtimeResults.fromLine(json)); - } - - /** Packages array contains non-object entries -> parsing should fail and return null. */ - @Test - void fromLine_PackagesArrayWithNonObjectEntries_ReturnsNull() { - String json = "{\"Packages\":[123,\"abc\"]}"; - Assertions.assertNull(OssRealtimeResults.fromLine(json)); - } - - /** Packages key absent entirely -> fromLine returns null. */ - @Test - void fromLine_LineDoesNotContainPackagesKey_ReturnsNull() { - String jsonLine = "{\"PackageManager\":\"npm\"}"; // No top-level "Packages" key - Assertions.assertNull(OssRealtimeResults.fromLine(jsonLine)); - } - - /** Truncated JSON containing Packages key -> parse exception caught, returns null. */ - @Test - void fromLine_TruncatedJsonWithPackagesKey_CatchesAndReturnsNull() { - String truncated = "{\"Packages\":[{"; // contains "Packages" but invalid JSON - Assertions.assertNull(OssRealtimeResults.fromLine(truncated)); - } - - /** Packages key present but explicitly null -> results object with empty list. */ - @Test - void fromLine_PackagesNull_YieldsEmptyList() { - String json = "{\"Packages\":null}"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - Assertions.assertTrue(results.getPackages().isEmpty()); - } - - /** Empty Packages array -> empty list returned. */ - @Test - void fromLine_EmptyPackagesArray() { - String json = "{\"Packages\":[]}"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - Assertions.assertTrue(results.getPackages().isEmpty()); - } - - /** Package missing optional fields: packageManager & packageVersion should map to null. */ - @Test - void parsePackageMissingFields_AllowsNulls() { - String json = "{ \"Packages\": [{ \"PackageName\": \"only-name\", \"FilePath\": \"package.json\", \"Status\": \"OK\" }] }"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - Assertions.assertEquals(1, results.getPackages().size()); - OssRealtimeScanPackage pkg = results.getPackages().get(0); - Assertions.assertNull(pkg.getPackageManager()); - Assertions.assertEquals("only-name", pkg.getPackageName()); - Assertions.assertNull(pkg.getPackageVersion()); - } - - /** Unicode characters preserved in Description field. */ - @Test - void parseUnicodeInDescription() { - String json = "{ \"Packages\": [{ \"PackageManager\": \"npm\", \"PackageName\": \"u\", \"PackageVersion\": \"1\", \"FilePath\": \"p.json\", \"Status\": \"OK\", \"Vulnerabilities\": [{ \"Id\": \"CVE-u\", \"Severity\": \"Low\", \"Description\": \"Unicode snow ☃ and emoji 🚀\" }] }] }"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - Assertions.assertEquals(1, results.getPackages().size()); - OssRealtimeVulnerability vul = results.getPackages().get(0).getVulnerabilities().get(0); - Assertions.assertEquals("Unicode snow ☃ and emoji 🚀", vul.getDescription()); - Assertions.assertEquals("CVE-u", vul.getId()); - } - - /** Multiple vulnerabilities: one without fixVersion, one with fixVersion. */ - @Test - void parseMultipleVulnerabilities() { - String json = "{\n" + - " \"Packages\": [{\n" + - " \"PackageManager\": \"npm\",\n" + - " \"PackageName\": \"dep\",\n" + - " \"PackageVersion\": \"1.0.0\",\n" + - " \"FilePath\": \"/a/package.json\",\n" + - " \"Status\": \"OK\",\n" + - " \"Vulnerabilities\": [\n" + - " { \"Id\": \"CVE-1\", \"Severity\": \"Low\", \"Description\": \"d1\" },\n" + - " { \"Id\": \"CVE-2\", \"Severity\": \"Critical\", \"Description\": \"d2\", \"FixVersion\": \"2.0.0\" }\n" + - " ]\n" + - " }]\n" + - "}"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - OssRealtimeScanPackage pkg = results.getPackages().get(0); - List vulns = pkg.getVulnerabilities(); - Assertions.assertEquals(2, vulns.size()); - Assertions.assertEquals("CVE-1", vulns.get(0).getId()); - Assertions.assertNull(vulns.get(0).getFixVersion()); - Assertions.assertEquals("CVE-2", vulns.get(1).getId()); - Assertions.assertEquals("2.0.0", vulns.get(1).getFixVersion()); - } - - /** Explicit null lists should be normalized to empty lists by constructor. */ - @Test - void constructor_DefaultsEmptyListsWhenNull() throws IOException { - String json = "{\n" + - " \"Packages\": [{\n" + - " \"PackageManager\": \"pip\",\n" + - " \"PackageName\": \"requests\",\n" + - " \"PackageVersion\": \"2.0.0\",\n" + - " \"FilePath\": \"requirements.txt\",\n" + - " \"Status\": \"Unknown\",\n" + - " \"Locations\": null,\n" + - " \"Vulnerabilities\": null\n" + - " }]\n" + - "}"; - OssRealtimeResults results = new ObjectMapper().readValue(json, OssRealtimeResults.class); - Assertions.assertNotNull(results); - Assertions.assertEquals(1, results.getPackages().size()); - OssRealtimeScanPackage pkg = results.getPackages().get(0); - Assertions.assertTrue(pkg.getLocations().isEmpty()); - Assertions.assertTrue(pkg.getVulnerabilities().isEmpty()); - } - - /** All vulnerability fields mapped including fixVersion. */ - @Test - void vulnerability_AllFieldsMapped() { - String json = "{\n" + - " \"Packages\": [{\n" + - " \"PackageManager\": \"npm\",\n" + - " \"PackageName\": \"chalk\",\n" + - " \"PackageVersion\": \"5.0.0\",\n" + - " \"FilePath\": \"/w/package.json\",\n" + - " \"Status\": \"OK\",\n" + - " \"Vulnerabilities\": [{\n" + - " \"Id\": \"CVE-2025-9999\",\n" + - " \"Severity\": \"Medium\",\n" + - " \"Description\": \"Some issue\",\n" + - " \"FixVersion\": \"5.0.1\"\n" + - " }]\n" + - " }]\n" + - "}"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - OssRealtimeVulnerability vul = results.getPackages().get(0).getVulnerabilities().get(0); - Assertions.assertEquals("CVE-2025-9999", vul.getId()); - Assertions.assertEquals("Medium", vul.getSeverity()); - Assertions.assertEquals("Some issue", vul.getDescription()); - Assertions.assertEquals("5.0.1", vul.getFixVersion()); - } -} diff --git a/src/test/resources/ignored-packages.json b/src/test/resources/ignored-packages.json index 2ebbedc2..5ec153d5 100644 --- a/src/test/resources/ignored-packages.json +++ b/src/test/resources/ignored-packages.json @@ -1 +1,5 @@ -[{"name": "marked", "version": "*"}] \ No newline at end of file +[ + {"name": "jackson-databind", "version": "*"}, + {"name": "commons-lang3", "version": "*"}, + {"name": "json-simple", "version": "*"} +] From a395d91c2867a455d6116e9869306ddf9f816bd9 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:02:09 +0530 Subject: [PATCH 09/37] Replaced Id to CVE in oss vulnerability --- .../checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java index 7ac9c574..ac89b368 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java @@ -12,17 +12,17 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeVulnerability { - @JsonProperty("Id") String id; + @JsonProperty("CVE") String cve; @JsonProperty("Severity") String severity; @JsonProperty("Description") String description; @JsonProperty("FixVersion") String fixVersion; @JsonCreator - public OssRealtimeVulnerability(@JsonProperty("Id") String id, + public OssRealtimeVulnerability(@JsonProperty("CVE") String cve, @JsonProperty("Severity") String severity, @JsonProperty("Description") String description, @JsonProperty("FixVersion") String fixVersion) { - this.id = id; + this.cve = cve; this.severity = severity; this.description = description; this.fixVersion = fixVersion; From 5564f93877c2bff7280236a26f55f57d0e416f88 Mon Sep 17 00:00:00 2001 From: cx-anand-nandeshwar <73646287+cx-anand-nandeshwar@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:17:35 +0530 Subject: [PATCH 10/37] Changed variable from id to CVE as per OSS response --- .../checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java index 7ac9c574..ac89b368 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java @@ -12,17 +12,17 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeVulnerability { - @JsonProperty("Id") String id; + @JsonProperty("CVE") String cve; @JsonProperty("Severity") String severity; @JsonProperty("Description") String description; @JsonProperty("FixVersion") String fixVersion; @JsonCreator - public OssRealtimeVulnerability(@JsonProperty("Id") String id, + public OssRealtimeVulnerability(@JsonProperty("CVE") String cve, @JsonProperty("Severity") String severity, @JsonProperty("Description") String description, @JsonProperty("FixVersion") String fixVersion) { - this.id = id; + this.cve = cve; this.severity = severity; this.description = description; this.fixVersion = fixVersion; From 1810e786026b6726f0c097f5980a75a74104c626 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:42:49 +0530 Subject: [PATCH 11/37] aimcp server changes --- .../checkmarx/ast/wrapper/CxConstants.java | 3 ++ .../com/checkmarx/ast/wrapper/CxWrapper.java | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index 5f1b8f62..393de4fd 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -61,6 +61,8 @@ public final class CxConstants { static final String ADDITONAL_PARAMS = "--additional-params"; static final String ENGINE = "--engine"; static final String SUB_CMD_KICS_REALTIME = "kics-realtime"; + static final String SUB_CMD_OSS_REALTIME = "oss-realtime"; + static final String IGNORED_FILE_PATH = "--ignored-file-path"; static final String SCA_REMEDIATION_PACKAGE_FILES = "--package-files"; static final String SCA_REMEDIATION_PACKAGE = "--package"; static final String SCA_REMEDIATION_PACKAGE_VERSION = "--package-version"; @@ -74,4 +76,5 @@ public final class CxConstants { static final String SUB_CMD_LEARN_MORE = "learn-more"; static final String SUB_CMD_TENANT = "tenant"; static final String IDE_SCANS_KEY = "scan.config.plugins.ideScans"; + static final String AI_MCP_SERVER_KEY = "scan.config.plugins.aiMcpServer"; } diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 38640d81..a7a2a508 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -404,6 +404,26 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String return Execution.executeCommand(withConfigArguments(arguments), logger, KicsRealtimeResults::fromLine); } + public String ossRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + throws IOException, InterruptedException, CxException { + this.logger.info("Executing 'scan oss-realtime' command using the CLI."); + this.logger.info("Source: {} IgnoredFilePath: {}", sourcePath, ignoredFilePath); + List arguments = new ArrayList<>(); + arguments.add(CxConstants.CMD_SCAN); + arguments.add(CxConstants.SUB_CMD_OSS_REALTIME); + arguments.add(CxConstants.SOURCE); + arguments.add(sourcePath); + if (StringUtils.isNotBlank(ignoredFilePath)) { + arguments.add(CxConstants.IGNORED_FILE_PATH); + arguments.add(ignoredFilePath); + } + return Execution.executeCommand(withConfigArguments(arguments), logger, line -> line); + } + + public String ossRealtimeScan(@NonNull String sourcePath) + throws IOException, InterruptedException, CxException { + return ossRealtimeScan(sourcePath, null); + } public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFile, String engine,String similarityIds) throws IOException, InterruptedException, CxException { this.logger.info("Executing 'remediation kics' command using the CLI."); @@ -455,6 +475,18 @@ public boolean ideScansEnabled() throws CxException, IOException, InterruptedExc .orElse(false); } + public boolean aiMcpServerEnabled() throws CxException, IOException, InterruptedException { + List tenantSettings = tenantSettings(); + if (tenantSettings == null) { + throw new CxException(1, "Unable to parse tenant settings"); + } + return tenantSettings.stream() + .filter(t -> t.getKey().equals(CxConstants.AI_MCP_SERVER_KEY)) + .findFirst() + .map(t -> Boolean.parseBoolean(t.getValue())) + .orElse(false); + } + public List tenantSettings() throws CxException, IOException, InterruptedException { List arguments = jsonArguments(); From bf95eb7bd83e48f51d6af7d2858ba8f6bf780440 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:11:07 +0530 Subject: [PATCH 12/37] oss-realtime scanner changes --- .../ast/ossrealtime/OssRealtimeLocation.java | 31 ++++++++++++ .../ast/ossrealtime/OssRealtimeResults.java | 50 +++++++++++++++++++ .../ossrealtime/OssRealtimeScanPackage.java | 49 ++++++++++++++++++ .../checkmarx/ast/wrapper/CxConstants.java | 7 ++- .../com/checkmarx/ast/wrapper/CxWrapper.java | 33 +++++++++--- 5 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java create mode 100644 src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java create mode 100644 src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java new file mode 100644 index 00000000..438d10c5 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java @@ -0,0 +1,31 @@ +package com.checkmarx.ast.ossrealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OssRealtimeLocation { + @JsonProperty("Line") + int line; + @JsonProperty("StartIndex") + int startIndex; + @JsonProperty("EndIndex") + int endIndex; + + @JsonCreator + public OssRealtimeLocation(@JsonProperty("Line") int line, + @JsonProperty("StartIndex") int startIndex, + @JsonProperty("EndIndex") int endIndex) { + this.line = line; + this.startIndex = startIndex; + this.endIndex = endIndex; + } +} + diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java new file mode 100644 index 00000000..f7f316c6 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java @@ -0,0 +1,50 @@ +package com.checkmarx.ast.ossrealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OssRealtimeResults { + @JsonProperty("Packages") + List packages; + + @JsonCreator + public OssRealtimeResults(@JsonProperty("Packages") List packages) { + this.packages = packages; + } + + public static OssRealtimeResults fromLine(String line) { + if (StringUtils.isBlank(line)) { + return null; + } + try { + if (isValidJSON(line) && line.contains("\"Packages\"")) { + return new ObjectMapper().readValue(line, OssRealtimeResults.class); + } + } catch (IOException ignored) { + } + return null; + } + + private static boolean isValidJSON(String json) { + try { + new ObjectMapper().readTree(json); + return true; + } catch (IOException e) { + return false; + } + } +} + diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java new file mode 100644 index 00000000..9d7d8479 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java @@ -0,0 +1,49 @@ +package com.checkmarx.ast.ossrealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OssRealtimeScanPackage { + @JsonProperty("PackageManager") + String packageManager; + @JsonProperty("PackageName") + String packageName; + @JsonProperty("PackageVersion") + String packageVersion; + @JsonProperty("FilePath") + String filePath; + @JsonProperty("Locations") + List locations; + @JsonProperty("Status") + String status; + @JsonProperty("Vulnerabilities") + List vulnerabilities; + + @JsonCreator + public OssRealtimeScanPackage(@JsonProperty("PackageManager") String packageManager, + @JsonProperty("PackageName") String packageName, + @JsonProperty("PackageVersion") String packageVersion, + @JsonProperty("FilePath") String filePath, + @JsonProperty("Locations") List locations, + @JsonProperty("Status") String status, + @JsonProperty("Vulnerabilities") List vulnerabilities) { + this.packageManager = packageManager; + this.packageName = packageName; + this.packageVersion = packageVersion; + this.filePath = filePath; + this.locations = locations; + this.status = status; + this.vulnerabilities = vulnerabilities; + } +} + diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index 393de4fd..57611763 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -61,8 +61,6 @@ public final class CxConstants { static final String ADDITONAL_PARAMS = "--additional-params"; static final String ENGINE = "--engine"; static final String SUB_CMD_KICS_REALTIME = "kics-realtime"; - static final String SUB_CMD_OSS_REALTIME = "oss-realtime"; - static final String IGNORED_FILE_PATH = "--ignored-file-path"; static final String SCA_REMEDIATION_PACKAGE_FILES = "--package-files"; static final String SCA_REMEDIATION_PACKAGE = "--package"; static final String SCA_REMEDIATION_PACKAGE_VERSION = "--package-version"; @@ -77,4 +75,9 @@ public final class CxConstants { static final String SUB_CMD_TENANT = "tenant"; static final String IDE_SCANS_KEY = "scan.config.plugins.ideScans"; static final String AI_MCP_SERVER_KEY = "scan.config.plugins.aiMcpServer"; + static final String IGNORED_FILE_PATH = "--ignored-file-path"; + static final String SUB_CMD_OSS_REALTIME = "oss-realtime"; + static final String SUB_CMD_IAC_REALTIME = "iac-realtime"; + static final String SUB_CMD_SECRETS_REALTIME = "secrets-realtime"; + static final String SUB_CMD_CONTAINERS_REALTIME = "containers-realtime"; } diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index a7a2a508..662e035c 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -4,6 +4,7 @@ import com.checkmarx.ast.codebashing.CodeBashing; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; import com.checkmarx.ast.learnMore.LearnMore; +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; import com.checkmarx.ast.predicate.CustomState; import com.checkmarx.ast.predicate.Predicate; import com.checkmarx.ast.project.Project; @@ -404,26 +405,46 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String return Execution.executeCommand(withConfigArguments(arguments), logger, KicsRealtimeResults::fromLine); } - public String ossRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String ignoredFilePath, java.util.function.Function resultParser) throws IOException, InterruptedException, CxException { - this.logger.info("Executing 'scan oss-realtime' command using the CLI."); + this.logger.info("Executing 'scan {}' command using the CLI.", subCommand); this.logger.info("Source: {} IgnoredFilePath: {}", sourcePath, ignoredFilePath); List arguments = new ArrayList<>(); arguments.add(CxConstants.CMD_SCAN); - arguments.add(CxConstants.SUB_CMD_OSS_REALTIME); + arguments.add(subCommand); arguments.add(CxConstants.SOURCE); arguments.add(sourcePath); if (StringUtils.isNotBlank(ignoredFilePath)) { arguments.add(CxConstants.IGNORED_FILE_PATH); arguments.add(ignoredFilePath); } - return Execution.executeCommand(withConfigArguments(arguments), logger, line -> line); + return Execution.executeCommand(withConfigArguments(arguments), logger, resultParser); + } + + // OSS Realtime + public OssRealtimeResults ossRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + throws IOException, InterruptedException, CxException { + return realtimeScan(CxConstants.SUB_CMD_OSS_REALTIME, sourcePath, ignoredFilePath, OssRealtimeResults::fromLine); } - public String ossRealtimeScan(@NonNull String sourcePath) + // IAC Realtime + public String iacRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return ossRealtimeScan(sourcePath, null); + return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath, ignoredFilePath, line -> line); } + + // Secrets Realtime + public String secretsRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + throws IOException, InterruptedException, CxException { + return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, line -> line); + } + + // Containers Realtime + public String containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + throws IOException, InterruptedException, CxException { + return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, ignoredFilePath, line -> line); + } + public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFile, String engine,String similarityIds) throws IOException, InterruptedException, CxException { this.logger.info("Executing 'remediation kics' command using the CLI."); From 7c7227436ccd847f250c4ba982fa580eab6f4555 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:16:45 +0530 Subject: [PATCH 13/37] Create OssRealtimeVulnerability.java --- .../ossrealtime/OssRealtimeVulnerability.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java new file mode 100644 index 00000000..857eb292 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java @@ -0,0 +1,19 @@ +package com.checkmarx.ast.ossrealtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OssRealtimeVulnerability { + @JsonProperty("Id") String id; + @JsonProperty("Severity") String severity; + @JsonProperty("Description") String description; + @JsonProperty("FixVersion") String fixVersion; +} + From 0677502fac8a661efd65fc1a2cbda8b48f721ffe Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:50:57 +0530 Subject: [PATCH 14/37] Unify realtime scan wrappers; consolidate Secrets/IaC models; deprecate and stub obsolete result classes --- .../ContainersRealtimeImage.java | 40 ++++++++ .../ContainersRealtimeResults.java | 54 ++++++++++ .../ContainersRealtimeVulnerability.java | 0 .../ast/iacRealtime/IacRealtimeResults.java | 98 +++++++++++++++++++ .../OssRealtimeResults.java | 9 +- .../OssRealtimeScanPackage.java | 13 +-- .../OssRealtimeVulnerability.java | 2 +- .../RealtimeLocation.java} | 19 ++-- .../SecretsRealtimeResults.java | 95 ++++++++++++++++++ .../com/checkmarx/ast/wrapper/CxWrapper.java | 18 ++-- 10 files changed, 319 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java create mode 100644 src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java create mode 100644 src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java create mode 100644 src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java rename src/main/java/com/checkmarx/ast/{ossrealtime => ossRealtime}/OssRealtimeResults.java (82%) rename src/main/java/com/checkmarx/ast/{ossrealtime => ossRealtime}/OssRealtimeScanPackage.java (82%) rename src/main/java/com/checkmarx/ast/{ossrealtime => ossRealtime}/OssRealtimeVulnerability.java (93%) rename src/main/java/com/checkmarx/ast/{ossrealtime/OssRealtimeLocation.java => realtime/RealtimeLocation.java} (56%) create mode 100644 src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java new file mode 100644 index 00000000..54789e49 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java @@ -0,0 +1,40 @@ +package com.checkmarx.ast.containersRealtime; + +import com.checkmarx.ast.realtime.RealtimeLocation; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +import java.util.Collections; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContainersRealtimeImage { + @JsonProperty("ImageName") String imageName; + @JsonProperty("ImageTag") String imageTag; + @JsonProperty("FilePath") String filePath; + @JsonProperty("Locations") List locations; + @JsonProperty("Status") String status; + @JsonProperty("Vulnerabilities") List vulnerabilities; + + @JsonCreator + public ContainersRealtimeImage(@JsonProperty("ImageName") String imageName, + @JsonProperty("ImageTag") String imageTag, + @JsonProperty("FilePath") String filePath, + @JsonProperty("Locations") List locations, + @JsonProperty("Status") String status, + @JsonProperty("Vulnerabilities") List vulnerabilities) { + this.imageName = imageName; + this.imageTag = imageTag; + this.filePath = filePath; + this.locations = locations == null ? Collections.emptyList() : locations; + this.status = status; + this.vulnerabilities = vulnerabilities == null ? Collections.emptyList() : vulnerabilities; + } +} diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java new file mode 100644 index 00000000..f40747cd --- /dev/null +++ b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java @@ -0,0 +1,54 @@ +package com.checkmarx.ast.containersRealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContainersRealtimeResults { + private static final Logger log = LoggerFactory.getLogger(ContainersRealtimeResults.class); + + @JsonProperty("Images") List images; + + @JsonCreator + public ContainersRealtimeResults(@JsonProperty("Images") List images) { + this.images = images; + } + + public static ContainersRealtimeResults fromLine(String line) { + if (StringUtils.isBlank(line)) { + return null; + } + try { + if (line.contains("\"Images\"") && isValidJSON(line)) { + return new ObjectMapper().readValue(line, ContainersRealtimeResults.class); + } + } catch (IOException e) { + log.debug("Failed to parse containers realtime line: {}", line, e); + } + return null; + } + + private static boolean isValidJSON(String json) { + try { + new ObjectMapper().readTree(json); + return true; + } catch (IOException e) { + return false; + } + } +} + diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java b/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java new file mode 100644 index 00000000..df4dffa8 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java @@ -0,0 +1,98 @@ +package com.checkmarx.ast.iacRealtime; + +import com.checkmarx.ast.realtime.RealtimeLocation; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class IacRealtimeResults { + private static final Logger log = LoggerFactory.getLogger(IacRealtimeResults.class); + @JsonProperty("Results") List results; // Normalized list (array or single object) + + @JsonCreator + public IacRealtimeResults(@JsonProperty("Results") List results) { + this.results = results == null ? Collections.emptyList() : results; + } + + @Value + @JsonDeserialize + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Issue { + @JsonProperty("Title") String title; + @JsonProperty("Description") String description; + @JsonProperty("SimilarityID") String similarityId; + @JsonProperty("FilePath") String filePath; + @JsonProperty("Severity") String severity; + @JsonProperty("ExpectedValue") String expectedValue; + @JsonProperty("ActualValue") String actualValue; + @JsonProperty("Locations") List locations; + + @JsonCreator + public Issue(@JsonProperty("Title") String title, + @JsonProperty("Description") String description, + @JsonProperty("SimilarityID") String similarityId, + @JsonProperty("FilePath") String filePath, + @JsonProperty("Severity") String severity, + @JsonProperty("ExpectedValue") String expectedValue, + @JsonProperty("ActualValue") String actualValue, + @JsonProperty("Locations") List locations) { + this.title = title; + this.description = description; + this.similarityId = similarityId; + this.filePath = filePath; + this.severity = severity; + this.expectedValue = expectedValue; + this.actualValue = actualValue; + this.locations = locations == null ? Collections.emptyList() : locations; + } + } + + public static IacRealtimeResults fromLine(String line) { + if (StringUtils.isBlank(line)) { + return null; + } + try { + if (!isValidJSON(line)) { + return null; + } + ObjectMapper mapper = new ObjectMapper(); + String trimmed = line.trim(); + if (trimmed.startsWith("[")) { + List list = mapper.readValue(trimmed, mapper.getTypeFactory().constructCollectionType(List.class, Issue.class)); + return new IacRealtimeResults(list == null ? Collections.emptyList() : list); + } + if (trimmed.startsWith("{")) { + Issue single = mapper.readValue(trimmed, Issue.class); + return new IacRealtimeResults(Collections.singletonList(single)); + } + } catch (IOException e) { + log.debug("Failed to parse iac realtime JSON line: {}", line, e); + } + return null; + } + + private static boolean isValidJSON(String json) { + try { + new ObjectMapper().readTree(json); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java similarity index 82% rename from src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java rename to src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java index f7f316c6..61a516c9 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossrealtime; +package com.checkmarx.ast.ossRealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -8,6 +8,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Value; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.List; @@ -17,6 +19,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeResults { + private static final Logger log = LoggerFactory.getLogger(OssRealtimeResults.class); @JsonProperty("Packages") List packages; @@ -33,7 +36,8 @@ public static OssRealtimeResults fromLine(String line) { if (isValidJSON(line) && line.contains("\"Packages\"")) { return new ObjectMapper().readValue(line, OssRealtimeResults.class); } - } catch (IOException ignored) { + } catch (IOException e) { + log.debug("Failed to parse oss realtime line: {}", line, e); } return null; } @@ -47,4 +51,3 @@ private static boolean isValidJSON(String json) { } } } - diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java similarity index 82% rename from src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java rename to src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java index 9d7d8479..d8a7149f 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java +++ b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java @@ -1,5 +1,6 @@ -package com.checkmarx.ast.ossrealtime; +package com.checkmarx.ast.ossRealtime; +import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -7,6 +8,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Value; +import java.util.Collections; import java.util.List; @Value @@ -23,7 +25,7 @@ public class OssRealtimeScanPackage { @JsonProperty("FilePath") String filePath; @JsonProperty("Locations") - List locations; + List locations; @JsonProperty("Status") String status; @JsonProperty("Vulnerabilities") @@ -34,16 +36,15 @@ public OssRealtimeScanPackage(@JsonProperty("PackageManager") String packageMana @JsonProperty("PackageName") String packageName, @JsonProperty("PackageVersion") String packageVersion, @JsonProperty("FilePath") String filePath, - @JsonProperty("Locations") List locations, + @JsonProperty("Locations") List locations, @JsonProperty("Status") String status, @JsonProperty("Vulnerabilities") List vulnerabilities) { this.packageManager = packageManager; this.packageName = packageName; this.packageVersion = packageVersion; this.filePath = filePath; - this.locations = locations; + this.locations = locations == null ? Collections.emptyList() : locations; this.status = status; - this.vulnerabilities = vulnerabilities; + this.vulnerabilities = vulnerabilities == null ? Collections.emptyList() : vulnerabilities; } } - diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java similarity index 93% rename from src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java rename to src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java index 857eb292..5379c111 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossrealtime; +package com.checkmarx.ast.ossRealtime; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java b/src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java similarity index 56% rename from src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java rename to src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java index 438d10c5..277fe13d 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeLocation.java +++ b/src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossrealtime; +package com.checkmarx.ast.realtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -11,18 +11,15 @@ @JsonDeserialize @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public class OssRealtimeLocation { - @JsonProperty("Line") - int line; - @JsonProperty("StartIndex") - int startIndex; - @JsonProperty("EndIndex") - int endIndex; +public class RealtimeLocation { + @JsonProperty("Line") int line; + @JsonProperty("StartIndex") int startIndex; + @JsonProperty("EndIndex") int endIndex; @JsonCreator - public OssRealtimeLocation(@JsonProperty("Line") int line, - @JsonProperty("StartIndex") int startIndex, - @JsonProperty("EndIndex") int endIndex) { + public RealtimeLocation(@JsonProperty("Line") int line, + @JsonProperty("StartIndex") int startIndex, + @JsonProperty("EndIndex") int endIndex) { this.line = line; this.startIndex = startIndex; this.endIndex = endIndex; diff --git a/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java b/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java new file mode 100644 index 00000000..d04d00db --- /dev/null +++ b/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java @@ -0,0 +1,95 @@ +package com.checkmarx.ast.secretsRealtime; + +import com.checkmarx.ast.realtime.RealtimeLocation; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretsRealtimeResults { + private static final Logger log = LoggerFactory.getLogger(SecretsRealtimeResults.class); + + @JsonProperty("Secrets") + List secrets; // Normalized list (array or single object from CLI) + + @JsonCreator + public SecretsRealtimeResults(@JsonProperty("Secrets") List secrets) { + this.secrets = secrets == null ? Collections.emptyList() : secrets; + } + + @Value + @JsonDeserialize + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Secret { + @JsonProperty("Title") String title; + @JsonProperty("Description") String description; + @JsonProperty("SecretValue") String secretValue; + @JsonProperty("FilePath") String filePath; + @JsonProperty("Severity") String severity; + @JsonProperty("Locations") List locations; + + @JsonCreator + public Secret(@JsonProperty("Title") String title, + @JsonProperty("Description") String description, + @JsonProperty("SecretValue") String secretValue, + @JsonProperty("FilePath") String filePath, + @JsonProperty("Severity") String severity, + @JsonProperty("Locations") List locations) { + this.title = title; + this.description = description; + this.secretValue = secretValue; + this.filePath = filePath; + this.severity = severity; + this.locations = locations == null ? Collections.emptyList() : locations; + } + } + + public static SecretsRealtimeResults fromLine(String line) { + if (StringUtils.isBlank(line)) { + return null; // skip blank + } + try { + if (!isValidJSON(line)) { + return null; + } + ObjectMapper mapper = new ObjectMapper(); + String trimmed = line.trim(); + if (trimmed.startsWith("[")) { // array form + List list = mapper.readValue(trimmed, + mapper.getTypeFactory().constructCollectionType(List.class, Secret.class)); + return new SecretsRealtimeResults(list); + } + if (trimmed.startsWith("{")) { // single object form + Secret single = mapper.readValue(trimmed, Secret.class); + return new SecretsRealtimeResults(Collections.singletonList(single)); + } + } catch (IOException e) { + log.debug("Failed to parse secrets realtime JSON line: {}", line, e); + } + return null; + } + + private static boolean isValidJSON(String json) { + try { + new ObjectMapper().readTree(json); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 662e035c..a8662bb9 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -4,7 +4,10 @@ import com.checkmarx.ast.codebashing.CodeBashing; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; import com.checkmarx.ast.learnMore.LearnMore; -import com.checkmarx.ast.ossrealtime.OssRealtimeResults; +import com.checkmarx.ast.ossRealtime.OssRealtimeResults; +import com.checkmarx.ast.secretsRealtime.SecretsRealtimeResults; +import com.checkmarx.ast.iacRealtime.IacRealtimeResults; +import com.checkmarx.ast.containersRealtime.ContainersRealtimeResults; import com.checkmarx.ast.predicate.CustomState; import com.checkmarx.ast.predicate.Predicate; import com.checkmarx.ast.project.Project; @@ -25,7 +28,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import java.lang.reflect.Field; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -428,21 +430,21 @@ public OssRealtimeResults ossRealtimeScan(@NonNull String sourcePath, String ign } // IAC Realtime - public String iacRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public IacRealtimeResults iacRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath, ignoredFilePath, line -> line); + return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath, ignoredFilePath, IacRealtimeResults::fromLine); } // Secrets Realtime - public String secretsRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public SecretsRealtimeResults secretsRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, line -> line); + return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, SecretsRealtimeResults::fromLine); } // Containers Realtime - public String containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public ContainersRealtimeResults containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, ignoredFilePath, line -> line); + return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, ignoredFilePath, ContainersRealtimeResults::fromLine); } public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFile, String engine,String similarityIds) From 2f0f55beaad5447a269a4e2da17ff4956536b54c Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:06:23 +0530 Subject: [PATCH 15/37] Add ContainersRealtimeVulnerability model for containers realtime scan parsing --- .../ContainersRealtimeVulnerability.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java index e69de29b..c40d4dda 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java @@ -0,0 +1,17 @@ +package com.checkmarx.ast.containersRealtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Value; + +@Value +@JsonDeserialize +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContainersRealtimeVulnerability { + @JsonProperty("CVE") String cve; + @JsonProperty("Severity") String severity; +} + From d1cb41c0f109752415951301018a3bfbec92d1c8 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:09:15 +0530 Subject: [PATCH 16/37] Add @JsonCreator constructor to OssRealtimeVulnerability for reliable Jackson deserialization --- .../ContainersRealtimeVulnerability.java | 9 ++++++++- .../ast/ossRealtime/OssRealtimeVulnerability.java | 14 +++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java index c40d4dda..e1458735 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java @@ -1,5 +1,6 @@ package com.checkmarx.ast.containersRealtime; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -13,5 +14,11 @@ public class ContainersRealtimeVulnerability { @JsonProperty("CVE") String cve; @JsonProperty("Severity") String severity; -} + @JsonCreator + public ContainersRealtimeVulnerability(@JsonProperty("CVE") String cve, + @JsonProperty("Severity") String severity) { + this.cve = cve; + this.severity = severity; + } +} diff --git a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java index 5379c111..00a4618a 100644 --- a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java @@ -1,5 +1,6 @@ package com.checkmarx.ast.ossRealtime; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -10,10 +11,21 @@ @JsonDeserialize @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) + public class OssRealtimeVulnerability { @JsonProperty("Id") String id; @JsonProperty("Severity") String severity; @JsonProperty("Description") String description; @JsonProperty("FixVersion") String fixVersion; -} + @JsonCreator + public OssRealtimeVulnerability(@JsonProperty("Id") String id, + @JsonProperty("Severity") String severity, + @JsonProperty("Description") String description, + @JsonProperty("FixVersion") String fixVersion) { + this.id = id; + this.severity = severity; + this.description = description; + this.fixVersion = fixVersion; + } +} From b2f258869e4d5b7587ab53102e00a3500caa9a32 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:37:57 +0530 Subject: [PATCH 17/37] Refactoring package name and adding test for oss and mcp flag --- .../ContainersRealtimeImage.java | 4 +- .../ContainersRealtimeResults.java | 3 +- .../ContainersRealtimeVulnerability.java | 4 +- .../IacRealtimeResults.java | 4 +- .../OssRealtimeResults.java | 10 +- .../OssRealtimeScanPackage.java | 24 +-- .../OssRealtimeVulnerability.java | 4 +- .../SecretsRealtimeResults.java | 15 +- .../com/checkmarx/ast/wrapper/CxWrapper.java | 14 +- src/test/java/com/checkmarx/ast/ScanTest.java | 15 ++ .../java/com/checkmarx/ast/TenantTest.java | 8 +- .../ast/unit/OssRealtimeParsingTest.java | 165 ++++++++++++++++++ src/test/resources/ignored-packages.json | 1 + 13 files changed, 226 insertions(+), 45 deletions(-) rename src/main/java/com/checkmarx/ast/{containersRealtime => containersrealtime}/ContainersRealtimeImage.java (97%) rename src/main/java/com/checkmarx/ast/{containersRealtime => containersrealtime}/ContainersRealtimeResults.java (97%) rename src/main/java/com/checkmarx/ast/{containersRealtime => containersrealtime}/ContainersRealtimeVulnerability.java (94%) rename src/main/java/com/checkmarx/ast/{iacRealtime => iacrealtime}/IacRealtimeResults.java (98%) rename src/main/java/com/checkmarx/ast/{ossRealtime => ossrealtime}/OssRealtimeResults.java (87%) rename src/main/java/com/checkmarx/ast/{ossRealtime => ossrealtime}/OssRealtimeScanPackage.java (76%) rename src/main/java/com/checkmarx/ast/{ossRealtime => ossrealtime}/OssRealtimeVulnerability.java (96%) rename src/main/java/com/checkmarx/ast/{secretsRealtime => secretsrealtime}/SecretsRealtimeResults.java (87%) create mode 100644 src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java create mode 100644 src/test/resources/ignored-packages.json diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeImage.java similarity index 97% rename from src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java rename to src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeImage.java index 54789e49..7a6a33a6 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeImage.java +++ b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeImage.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.containersRealtime; +package com.checkmarx.ast.containersrealtime; import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -37,4 +37,4 @@ public ContainersRealtimeImage(@JsonProperty("ImageName") String imageName, this.status = status; this.vulnerabilities = vulnerabilities == null ? Collections.emptyList() : vulnerabilities; } -} +} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeResults.java similarity index 97% rename from src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java rename to src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeResults.java index f40747cd..f9054ffe 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.containersRealtime; +package com.checkmarx.ast.containersrealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -51,4 +51,3 @@ private static boolean isValidJSON(String json) { } } } - diff --git a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeVulnerability.java similarity index 94% rename from src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java rename to src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeVulnerability.java index e1458735..cabe4b68 100644 --- a/src/main/java/com/checkmarx/ast/containersRealtime/ContainersRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeVulnerability.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.containersRealtime; +package com.checkmarx.ast.containersrealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -21,4 +21,4 @@ public ContainersRealtimeVulnerability(@JsonProperty("CVE") String cve, this.cve = cve; this.severity = severity; } -} +} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java b/src/main/java/com/checkmarx/ast/iacrealtime/IacRealtimeResults.java similarity index 98% rename from src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java rename to src/main/java/com/checkmarx/ast/iacrealtime/IacRealtimeResults.java index df4dffa8..0afc103f 100644 --- a/src/main/java/com/checkmarx/ast/iacRealtime/IacRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/iacrealtime/IacRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.iacRealtime; +package com.checkmarx.ast.iacrealtime; import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -95,4 +95,4 @@ private static boolean isValidJSON(String json) { return false; } } -} +} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java similarity index 87% rename from src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java rename to src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java index 61a516c9..7141370f 100644 --- a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossRealtime; +package com.checkmarx.ast.ossrealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.Collections; import java.util.List; @Value @@ -20,12 +21,12 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeResults { private static final Logger log = LoggerFactory.getLogger(OssRealtimeResults.class); - @JsonProperty("Packages") - List packages; + + @JsonProperty("Packages") List packages; @JsonCreator public OssRealtimeResults(@JsonProperty("Packages") List packages) { - this.packages = packages; + this.packages = packages == null ? Collections.emptyList() : packages; } public static OssRealtimeResults fromLine(String line) { @@ -51,3 +52,4 @@ private static boolean isValidJSON(String json) { } } } + diff --git a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java similarity index 76% rename from src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java rename to src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java index d8a7149f..d4b32771 100644 --- a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeScanPackage.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossRealtime; +package com.checkmarx.ast.ossrealtime; import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -16,20 +16,13 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeScanPackage { - @JsonProperty("PackageManager") - String packageManager; - @JsonProperty("PackageName") - String packageName; - @JsonProperty("PackageVersion") - String packageVersion; - @JsonProperty("FilePath") - String filePath; - @JsonProperty("Locations") - List locations; - @JsonProperty("Status") - String status; - @JsonProperty("Vulnerabilities") - List vulnerabilities; + @JsonProperty("PackageManager") String packageManager; + @JsonProperty("PackageName") String packageName; + @JsonProperty("PackageVersion") String packageVersion; + @JsonProperty("FilePath") String filePath; + @JsonProperty("Locations") List locations; + @JsonProperty("Status") String status; + @JsonProperty("Vulnerabilities") List vulnerabilities; @JsonCreator public OssRealtimeScanPackage(@JsonProperty("PackageManager") String packageManager, @@ -48,3 +41,4 @@ public OssRealtimeScanPackage(@JsonProperty("PackageManager") String packageMana this.vulnerabilities = vulnerabilities == null ? Collections.emptyList() : vulnerabilities; } } + diff --git a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java similarity index 96% rename from src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java rename to src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java index 00a4618a..7ac9c574 100644 --- a/src/main/java/com/checkmarx/ast/ossRealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.ossRealtime; +package com.checkmarx.ast.ossrealtime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -11,7 +11,6 @@ @JsonDeserialize @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) - public class OssRealtimeVulnerability { @JsonProperty("Id") String id; @JsonProperty("Severity") String severity; @@ -29,3 +28,4 @@ public OssRealtimeVulnerability(@JsonProperty("Id") String id, this.fixVersion = fixVersion; } } + diff --git a/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java b/src/main/java/com/checkmarx/ast/secretsrealtime/SecretsRealtimeResults.java similarity index 87% rename from src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java rename to src/main/java/com/checkmarx/ast/secretsrealtime/SecretsRealtimeResults.java index d04d00db..ef19050c 100644 --- a/src/main/java/com/checkmarx/ast/secretsRealtime/SecretsRealtimeResults.java +++ b/src/main/java/com/checkmarx/ast/secretsrealtime/SecretsRealtimeResults.java @@ -1,4 +1,4 @@ -package com.checkmarx.ast.secretsRealtime; +package com.checkmarx.ast.secretsrealtime; import com.checkmarx.ast.realtime.RealtimeLocation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -23,8 +23,7 @@ public class SecretsRealtimeResults { private static final Logger log = LoggerFactory.getLogger(SecretsRealtimeResults.class); - @JsonProperty("Secrets") - List secrets; // Normalized list (array or single object from CLI) + @JsonProperty("Secrets") List secrets; @JsonCreator public SecretsRealtimeResults(@JsonProperty("Secrets") List secrets) { @@ -61,7 +60,7 @@ public Secret(@JsonProperty("Title") String title, public static SecretsRealtimeResults fromLine(String line) { if (StringUtils.isBlank(line)) { - return null; // skip blank + return null; } try { if (!isValidJSON(line)) { @@ -69,12 +68,11 @@ public static SecretsRealtimeResults fromLine(String line) { } ObjectMapper mapper = new ObjectMapper(); String trimmed = line.trim(); - if (trimmed.startsWith("[")) { // array form - List list = mapper.readValue(trimmed, - mapper.getTypeFactory().constructCollectionType(List.class, Secret.class)); + if (trimmed.startsWith("[")) { + List list = mapper.readValue(trimmed, mapper.getTypeFactory().constructCollectionType(List.class, Secret.class)); return new SecretsRealtimeResults(list); } - if (trimmed.startsWith("{")) { // single object form + if (trimmed.startsWith("{")) { Secret single = mapper.readValue(trimmed, Secret.class); return new SecretsRealtimeResults(Collections.singletonList(single)); } @@ -93,3 +91,4 @@ private static boolean isValidJSON(String json) { } } } + diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index a8662bb9..85be850d 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -4,10 +4,10 @@ import com.checkmarx.ast.codebashing.CodeBashing; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; import com.checkmarx.ast.learnMore.LearnMore; -import com.checkmarx.ast.ossRealtime.OssRealtimeResults; -import com.checkmarx.ast.secretsRealtime.SecretsRealtimeResults; -import com.checkmarx.ast.iacRealtime.IacRealtimeResults; -import com.checkmarx.ast.containersRealtime.ContainersRealtimeResults; +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; +import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; +import com.checkmarx.ast.iacrealtime.IacRealtimeResults; +import com.checkmarx.ast.containersrealtime.ContainersRealtimeResults; import com.checkmarx.ast.predicate.CustomState; import com.checkmarx.ast.predicate.Predicate; import com.checkmarx.ast.project.Project; @@ -399,7 +399,7 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String arguments.add(fileSources); arguments.add(CxConstants.ADDITONAL_PARAMS); arguments.add(additionalParams); - if (engine.length() > 0) { + if (!engine.isEmpty()) { arguments.add(CxConstants.ENGINE); arguments.add(engine); } @@ -461,11 +461,11 @@ public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFil arguments.add(resultsFile); arguments.add(CxConstants.KICS_REMEDIATION_KICS_FILE); arguments.add(kicsFile); - if (engine.length() > 0) { + if (!engine.isEmpty()) { arguments.add(CxConstants.ENGINE); arguments.add(engine); } - if (similarityIds.length() > 0) { + if (!similarityIds.isEmpty()) { arguments.add(CxConstants.KICS_REMEDIATION_SIMILARITY); arguments.add(similarityIds); } diff --git a/src/test/java/com/checkmarx/ast/ScanTest.java b/src/test/java/com/checkmarx/ast/ScanTest.java index fb0b3b3b..5e31b337 100644 --- a/src/test/java/com/checkmarx/ast/ScanTest.java +++ b/src/test/java/com/checkmarx/ast/ScanTest.java @@ -3,7 +3,9 @@ import com.checkmarx.ast.asca.ScanDetail; import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; import com.checkmarx.ast.scan.Scan; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -92,4 +94,17 @@ void testKicsRealtimeScan() throws Exception { Assertions.assertTrue(scan.getResults().size() >= 1); } + @Test + void testOssRealtimeScanWithIgnoredFile() throws Exception { + Assumptions.assumeTrue(getConfig().getPathToExecutable() != null && !getConfig().getPathToExecutable().isEmpty(), "PATH_TO_EXECUTABLE not set"); + + String source = "pom.xml"; + String ignoreFile = "src/test/resources/ignored-packages.json"; + + OssRealtimeResults results = wrapper.ossRealtimeScan(source, ignoreFile); + + Assertions.assertNotNull(results); + Assertions.assertNotNull(results.getPackages()); + } + } diff --git a/src/test/java/com/checkmarx/ast/TenantTest.java b/src/test/java/com/checkmarx/ast/TenantTest.java index b9ac752c..7f49da16 100644 --- a/src/test/java/com/checkmarx/ast/TenantTest.java +++ b/src/test/java/com/checkmarx/ast/TenantTest.java @@ -11,11 +11,17 @@ public class TenantTest extends BaseTest { @Test void testTenantSettings() throws Exception { List tenantSettings = wrapper.tenantSettings(); - Assertions.assertTrue(tenantSettings.size() > 0); + Assertions.assertFalse(tenantSettings.isEmpty()); } @Test void testIdeScansEnabled() { Assertions.assertDoesNotThrow(() -> wrapper.ideScansEnabled()); } + + @Test + void testAiMcpServerEnabled() throws Exception { + boolean enabled = Assertions.assertDoesNotThrow(() -> wrapper.aiMcpServerEnabled()); + Assertions.assertTrue(enabled, "AI MCP Server flag expected to be true"); + } } diff --git a/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java b/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java new file mode 100644 index 00000000..f2ab9b4c --- /dev/null +++ b/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java @@ -0,0 +1,165 @@ +package com.checkmarx.ast.unit; + +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; +import com.checkmarx.ast.ossrealtime.OssRealtimeScanPackage; +import com.checkmarx.ast.ossrealtime.OssRealtimeVulnerability; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +/** + * Unit tests for OssRealtimeResults JSON parsing and object construction. + * Focus: JSON parsing branches, constructor defaults, null/empty handling. + */ +class OssRealtimeParsingTest { + + /** Packages value is a string -> parser should fail and return null. */ + @Test + void fromLine_PackagesStringType_ReturnsNull() { + String json = "{\"Packages\":\"oops\"}"; + Assertions.assertNull(OssRealtimeResults.fromLine(json)); + } + + /** Packages array contains non-object entries -> parsing should fail and return null. */ + @Test + void fromLine_PackagesArrayWithNonObjectEntries_ReturnsNull() { + String json = "{\"Packages\":[123,\"abc\"]}"; + Assertions.assertNull(OssRealtimeResults.fromLine(json)); + } + + /** Packages key absent entirely -> fromLine returns null. */ + @Test + void fromLine_LineDoesNotContainPackagesKey_ReturnsNull() { + String jsonLine = "{\"PackageManager\":\"npm\"}"; // No top-level "Packages" key + Assertions.assertNull(OssRealtimeResults.fromLine(jsonLine)); + } + + /** Truncated JSON containing Packages key -> parse exception caught, returns null. */ + @Test + void fromLine_TruncatedJsonWithPackagesKey_CatchesAndReturnsNull() { + String truncated = "{\"Packages\":[{"; // contains "Packages" but invalid JSON + Assertions.assertNull(OssRealtimeResults.fromLine(truncated)); + } + + /** Packages key present but explicitly null -> results object with empty list. */ + @Test + void fromLine_PackagesNull_YieldsEmptyList() { + String json = "{\"Packages\":null}"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + Assertions.assertTrue(results.getPackages().isEmpty()); + } + + /** Empty Packages array -> empty list returned. */ + @Test + void fromLine_EmptyPackagesArray() { + String json = "{\"Packages\":[]}"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + Assertions.assertTrue(results.getPackages().isEmpty()); + } + + /** Package missing optional fields: packageManager & packageVersion should map to null. */ + @Test + void parsePackageMissingFields_AllowsNulls() { + String json = "{ \"Packages\": [{ \"PackageName\": \"only-name\", \"FilePath\": \"package.json\", \"Status\": \"OK\" }] }"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getPackages().size()); + OssRealtimeScanPackage pkg = results.getPackages().get(0); + Assertions.assertNull(pkg.getPackageManager()); + Assertions.assertEquals("only-name", pkg.getPackageName()); + Assertions.assertNull(pkg.getPackageVersion()); + } + + /** Unicode characters preserved in Description field. */ + @Test + void parseUnicodeInDescription() { + String json = "{ \"Packages\": [{ \"PackageManager\": \"npm\", \"PackageName\": \"u\", \"PackageVersion\": \"1\", \"FilePath\": \"p.json\", \"Status\": \"OK\", \"Vulnerabilities\": [{ \"Id\": \"CVE-u\", \"Severity\": \"Low\", \"Description\": \"Unicode snow ☃ and emoji 🚀\" }] }] }"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getPackages().size()); + OssRealtimeVulnerability vul = results.getPackages().get(0).getVulnerabilities().get(0); + Assertions.assertEquals("Unicode snow ☃ and emoji 🚀", vul.getDescription()); + Assertions.assertEquals("CVE-u", vul.getId()); + } + + /** Multiple vulnerabilities: one without fixVersion, one with fixVersion. */ + @Test + void parseMultipleVulnerabilities() { + String json = "{\n" + + " \"Packages\": [{\n" + + " \"PackageManager\": \"npm\",\n" + + " \"PackageName\": \"dep\",\n" + + " \"PackageVersion\": \"1.0.0\",\n" + + " \"FilePath\": \"/a/package.json\",\n" + + " \"Status\": \"OK\",\n" + + " \"Vulnerabilities\": [\n" + + " { \"Id\": \"CVE-1\", \"Severity\": \"Low\", \"Description\": \"d1\" },\n" + + " { \"Id\": \"CVE-2\", \"Severity\": \"Critical\", \"Description\": \"d2\", \"FixVersion\": \"2.0.0\" }\n" + + " ]\n" + + " }]\n" + + "}"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + OssRealtimeScanPackage pkg = results.getPackages().get(0); + List vulns = pkg.getVulnerabilities(); + Assertions.assertEquals(2, vulns.size()); + Assertions.assertEquals("CVE-1", vulns.get(0).getId()); + Assertions.assertNull(vulns.get(0).getFixVersion()); + Assertions.assertEquals("CVE-2", vulns.get(1).getId()); + Assertions.assertEquals("2.0.0", vulns.get(1).getFixVersion()); + } + + /** Explicit null lists should be normalized to empty lists by constructor. */ + @Test + void constructor_DefaultsEmptyListsWhenNull() throws IOException { + String json = "{\n" + + " \"Packages\": [{\n" + + " \"PackageManager\": \"pip\",\n" + + " \"PackageName\": \"requests\",\n" + + " \"PackageVersion\": \"2.0.0\",\n" + + " \"FilePath\": \"requirements.txt\",\n" + + " \"Status\": \"Unknown\",\n" + + " \"Locations\": null,\n" + + " \"Vulnerabilities\": null\n" + + " }]\n" + + "}"; + OssRealtimeResults results = new ObjectMapper().readValue(json, OssRealtimeResults.class); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getPackages().size()); + OssRealtimeScanPackage pkg = results.getPackages().get(0); + Assertions.assertTrue(pkg.getLocations().isEmpty()); + Assertions.assertTrue(pkg.getVulnerabilities().isEmpty()); + } + + /** All vulnerability fields mapped including fixVersion. */ + @Test + void vulnerability_AllFieldsMapped() { + String json = "{\n" + + " \"Packages\": [{\n" + + " \"PackageManager\": \"npm\",\n" + + " \"PackageName\": \"chalk\",\n" + + " \"PackageVersion\": \"5.0.0\",\n" + + " \"FilePath\": \"/w/package.json\",\n" + + " \"Status\": \"OK\",\n" + + " \"Vulnerabilities\": [{\n" + + " \"Id\": \"CVE-2025-9999\",\n" + + " \"Severity\": \"Medium\",\n" + + " \"Description\": \"Some issue\",\n" + + " \"FixVersion\": \"5.0.1\"\n" + + " }]\n" + + " }]\n" + + "}"; + OssRealtimeResults results = OssRealtimeResults.fromLine(json); + Assertions.assertNotNull(results); + OssRealtimeVulnerability vul = results.getPackages().get(0).getVulnerabilities().get(0); + Assertions.assertEquals("CVE-2025-9999", vul.getId()); + Assertions.assertEquals("Medium", vul.getSeverity()); + Assertions.assertEquals("Some issue", vul.getDescription()); + Assertions.assertEquals("5.0.1", vul.getFixVersion()); + } +} diff --git a/src/test/resources/ignored-packages.json b/src/test/resources/ignored-packages.json new file mode 100644 index 00000000..2ebbedc2 --- /dev/null +++ b/src/test/resources/ignored-packages.json @@ -0,0 +1 @@ +[{"name": "marked", "version": "*"}] \ No newline at end of file From d647f57d58f177f2720020db8777e2c45bd49da4 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:29:59 +0530 Subject: [PATCH 18/37] Add integration tests for OSS, Container, and Secrets realtime scanners --- .../ast/ContainersRealtimeResultsTest.java | 241 +++++++++++++++ .../checkmarx/ast/IacRealtimeResultsTest.java | 60 ++++ .../checkmarx/ast/OssRealtimeParsingTest.java | 157 ++++++++++ .../ast/SecretsRealtimeResultsTest.java | 292 ++++++++++++++++++ .../ast/unit/OssRealtimeParsingTest.java | 165 ---------- src/test/resources/ignored-packages.json | 6 +- 6 files changed, 755 insertions(+), 166 deletions(-) create mode 100644 src/test/java/com/checkmarx/ast/ContainersRealtimeResultsTest.java create mode 100644 src/test/java/com/checkmarx/ast/IacRealtimeResultsTest.java create mode 100644 src/test/java/com/checkmarx/ast/OssRealtimeParsingTest.java create mode 100644 src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java delete mode 100644 src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java diff --git a/src/test/java/com/checkmarx/ast/ContainersRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/ContainersRealtimeResultsTest.java new file mode 100644 index 00000000..6911e4af --- /dev/null +++ b/src/test/java/com/checkmarx/ast/ContainersRealtimeResultsTest.java @@ -0,0 +1,241 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.containersrealtime.ContainersRealtimeImage; +import com.checkmarx.ast.containersrealtime.ContainersRealtimeResults; +import com.checkmarx.ast.containersrealtime.ContainersRealtimeVulnerability; +import com.checkmarx.ast.wrapper.CxException; +import org.junit.jupiter.api.*; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration and unit tests for Container Realtime scanner functionality. + * Tests the complete workflow: CLI invocation -> JSON parsing -> domain object mapping. + * Integration tests use Dockerfile as the scan target and are assumption-guarded for CI/local flexibility. + */ +class ContainersRealtimeResultsTest extends BaseTest { + + private boolean isCliConfigured() { + return Optional.ofNullable(getConfig().getPathToExecutable()).filter(s -> !s.isEmpty()).isPresent(); + } + + /* ------------------------------------------------------ */ + /* Integration tests for Container Realtime scanning */ + /* ------------------------------------------------------ */ + + /** + * Tests basic container realtime scan functionality on Dockerfile. + * Verifies that the scan returns a valid results object with detected container images. + * This test validates the end-to-end workflow from CLI execution to domain object creation. + */ + @Test + @DisplayName("Basic container scan on Dockerfile returns detected images") + void basicContainerRealtimeScan() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String dockerfilePath = "src/test/resources/Dockerfile"; + Assumptions.assumeTrue(Files.exists(Paths.get(dockerfilePath)), "Dockerfile not found - cannot test container scanning"); + + ContainersRealtimeResults results = wrapper.containersRealtimeScan(dockerfilePath, ""); + + assertNotNull(results, "Scan should return non-null results"); + assertNotNull(results.getImages(), "Images list should be initialized"); + + // Verify that if images are detected, they have proper structure + if (!results.getImages().isEmpty()) { + results.getImages().forEach(image -> { + assertNotNull(image.getImageName(), "Image name should be populated"); + assertNotNull(image.getVulnerabilities(), "Vulnerabilities list should be initialized"); + }); + } + } + + /** + * Tests container scan with ignore file functionality. + * Verifies that providing an ignore file doesn't break the scanning process + * and produces consistent or reduced results compared to baseline scan. + */ + @Test + @DisplayName("Container scan with ignore file works correctly") + void containerRealtimeScanWithIgnoreFile() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String dockerfilePath = "src/test/resources/Dockerfile"; + String ignoreFile = "src/test/resources/ignored-packages.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(dockerfilePath)) && Files.exists(Paths.get(ignoreFile)), + "Required test resources missing - cannot test ignore functionality"); + + ContainersRealtimeResults baseline = wrapper.containersRealtimeScan(dockerfilePath, ""); + ContainersRealtimeResults filtered = wrapper.containersRealtimeScan(dockerfilePath, ignoreFile); + + assertNotNull(baseline, "Baseline scan should return results"); + assertNotNull(filtered, "Filtered scan should return results"); + + // Ignore file should not increase the number of detected issues + if (baseline.getImages() != null && filtered.getImages() != null) { + assertTrue(filtered.getImages().size() <= baseline.getImages().size(), + "Filtered scan should not have more images than baseline"); + } + } + + /** + * Tests scan consistency by running the same container scan multiple times. + * Verifies that repeated scans of the same Dockerfile produce stable, deterministic results. + * This is important for CI/CD pipelines where consistent results are crucial. + */ + @Test + @DisplayName("Repeated container scans produce consistent results") + void containerRealtimeScanConsistency() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String dockerfilePath = "src/test/resources/Dockerfile"; + Assumptions.assumeTrue(Files.exists(Paths.get(dockerfilePath)), "Dockerfile not found - cannot test consistency"); + + ContainersRealtimeResults firstScan = wrapper.containersRealtimeScan(dockerfilePath, ""); + ContainersRealtimeResults secondScan = wrapper.containersRealtimeScan(dockerfilePath, ""); + + assertNotNull(firstScan, "First scan should return results"); + assertNotNull(secondScan, "Second scan should return results"); + + // Compare image counts for consistency + int firstImageCount = (firstScan.getImages() != null) ? firstScan.getImages().size() : 0; + int secondImageCount = (secondScan.getImages() != null) ? secondScan.getImages().size() : 0; + + assertEquals(firstImageCount, secondImageCount, + "Image count should be consistent across multiple scans"); + } + + /** + * Tests domain object mapping for container scan results. + * Verifies that JSON responses are properly parsed into domain objects + * and all expected fields are correctly mapped and initialized. + */ + @Test + @DisplayName("Container domain objects are properly mapped from scan results") + void containerDomainObjectMapping() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String dockerfilePath = "src/test/resources/Dockerfile"; + Assumptions.assumeTrue(Files.exists(Paths.get(dockerfilePath)), "Dockerfile not found - cannot test mapping"); + + ContainersRealtimeResults results = wrapper.containersRealtimeScan(dockerfilePath, ""); + assertNotNull(results, "Scan results should not be null"); + + // If images are detected, validate their structure + if (results.getImages() != null && !results.getImages().isEmpty()) { + ContainersRealtimeImage sampleImage = results.getImages().get(0); + + // Verify core image fields are mapped correctly + assertNotNull(sampleImage.getImageName(), "Image name should always be present"); + assertNotNull(sampleImage.getVulnerabilities(), "Vulnerabilities list should be initialized"); + + // If vulnerabilities exist, validate their structure + if (!sampleImage.getVulnerabilities().isEmpty()) { + ContainersRealtimeVulnerability sampleVuln = sampleImage.getVulnerabilities().get(0); + // CVE and Severity are the core fields that should be present + assertTrue(sampleVuln.getCve() != null || sampleVuln.getSeverity() != null, + "Vulnerability should have at least CVE or Severity information"); + } + } + } + + /** + * Tests error handling when scanning a non-existent file. + * Verifies that the scanner properly throws a CxException with meaningful error message + * when provided with invalid file paths, demonstrating proper error handling. + */ + @Test + @DisplayName("Container scan throws appropriate exception for non-existent file") + void containerScanHandlesInvalidPath() { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + // Test with a non-existent file path + String invalidPath = "src/test/resources/NonExistentDockerfile"; + + // The CLI should throw a CxException with a meaningful error message for invalid paths + CxException exception = assertThrows(CxException.class, () -> + wrapper.containersRealtimeScan(invalidPath, "") + ); + + // Verify the exception contains information about the invalid file path + String errorMessage = exception.getMessage(); + assertNotNull(errorMessage, "Exception should contain an error message"); + assertTrue(errorMessage.contains("invalid file path") || errorMessage.contains("file") || errorMessage.contains("path"), + "Exception message should indicate the issue is related to file path: " + errorMessage); + } + + /* ------------------------------------------------------ */ + /* Unit tests for JSON parsing robustness */ + /* ------------------------------------------------------ */ + + /** + * Tests JSON parsing with valid container scan response. + * Verifies that well-formed JSON is correctly parsed into domain objects. + */ + @Test + @DisplayName("Valid JSON parsing creates correct domain objects") + void testFromLineWithValidJson() { + String json = "{" + + "\"Images\": [" + + " {" + + " \"ImageName\": \"nginx:latest\"," + + " \"Vulnerabilities\": [" + + " {" + + " \"CVE\": \"CVE-2021-2345\"," + + " \"Severity\": \"High\"" + + " }" + + " ]" + + " }" + + "]" + + "}"; + ContainersRealtimeResults results = ContainersRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getImages().size()); + ContainersRealtimeImage image = results.getImages().get(0); + assertEquals("nginx:latest", image.getImageName()); + assertEquals(1, image.getVulnerabilities().size()); + ContainersRealtimeVulnerability vulnerability = image.getVulnerabilities().get(0); + assertEquals("CVE-2021-2345", vulnerability.getCve()); + assertEquals("High", vulnerability.getSeverity()); + } + + /** + * Tests parsing robustness with malformed JSON. + * Verifies that the parser gracefully handles various edge cases. + */ + @Test + @DisplayName("Malformed JSON is handled gracefully") + void testFromLineWithEdgeCases() { + // Missing Images key + assertNull(ContainersRealtimeResults.fromLine("{\"some_other_key\": \"some_value\"}")); + + // Invalid JSON structure + assertNull(ContainersRealtimeResults.fromLine("{\"Images\": [}")); + + // Blank/null inputs + assertNull(ContainersRealtimeResults.fromLine("")); + assertNull(ContainersRealtimeResults.fromLine(" ")); + assertNull(ContainersRealtimeResults.fromLine(null)); + } + + /** + * Tests parsing with empty or null image arrays. + * Verifies that empty results are handled correctly. + */ + @Test + @DisplayName("Empty and null image arrays are handled correctly") + void testFromLineWithEmptyResults() { + // Empty images array + String emptyJson = "{\"Images\": []}"; + ContainersRealtimeResults emptyResults = ContainersRealtimeResults.fromLine(emptyJson); + assertNotNull(emptyResults); + assertTrue(emptyResults.getImages().isEmpty()); + + // Null images + String nullJson = "{\"Images\": null}"; + ContainersRealtimeResults nullResults = ContainersRealtimeResults.fromLine(nullJson); + assertNotNull(nullResults); + assertNull(nullResults.getImages()); + } +} + diff --git a/src/test/java/com/checkmarx/ast/IacRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/IacRealtimeResultsTest.java new file mode 100644 index 00000000..ce7c8b7f --- /dev/null +++ b/src/test/java/com/checkmarx/ast/IacRealtimeResultsTest.java @@ -0,0 +1,60 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.iacrealtime.IacRealtimeResults; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class IacRealtimeResultsTest { + + @Test + void testFromLineWithValidJsonArray() { + String json = "[" + + " {" + + " \"Title\": \"My Issue\"," + + " \"Severity\": \"High\"" + + " }" + + "]"; + IacRealtimeResults results = IacRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getResults().size()); + IacRealtimeResults.Issue issue = results.getResults().get(0); + assertEquals("My Issue", issue.getTitle()); + assertEquals("High", issue.getSeverity()); + } + + @Test + void testFromLineWithValidJsonObject() { + String json = "{" + + " \"Title\": \"My Single Issue\"," + + " \"Severity\": \"Medium\"" + + "}"; + IacRealtimeResults results = IacRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getResults().size()); + IacRealtimeResults.Issue issue = results.getResults().get(0); + assertEquals("My Single Issue", issue.getTitle()); + assertEquals("Medium", issue.getSeverity()); + } + + @Test + void testFromLineWithEmptyJsonArray() { + String json = "[]"; + IacRealtimeResults results = IacRealtimeResults.fromLine(json); + assertNotNull(results); + assertTrue(results.getResults().isEmpty()); + } + + @Test + void testFromLineWithBlankLine() { + assertNull(IacRealtimeResults.fromLine("")); + assertNull(IacRealtimeResults.fromLine(" ")); + assertNull(IacRealtimeResults.fromLine(null)); + } + + @Test + void testFromLineWithInvalidJson() { + String json = "[{]"; + assertNull(IacRealtimeResults.fromLine(json)); + } +} + diff --git a/src/test/java/com/checkmarx/ast/OssRealtimeParsingTest.java b/src/test/java/com/checkmarx/ast/OssRealtimeParsingTest.java new file mode 100644 index 00000000..6ea0fea0 --- /dev/null +++ b/src/test/java/com/checkmarx/ast/OssRealtimeParsingTest.java @@ -0,0 +1,157 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.ossrealtime.OssRealtimeResults; +import com.checkmarx.ast.ossrealtime.OssRealtimeScanPackage; +import org.junit.jupiter.api.*; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests for OSS Realtime scanner functionality. + * Tests the complete workflow: CLI invocation -> JSON parsing -> domain object mapping. + * All tests use pom.xml as the scan target and are assumption-guarded for CI/local flexibility. + */ +class OssRealtimeParsingTest extends BaseTest { + + private boolean isCliConfigured() { + return Optional.ofNullable(getConfig().getPathToExecutable()).filter(s -> !s.isEmpty()).isPresent(); + } + + /** + * Tests basic OSS realtime scan functionality on pom.xml. + * Verifies that the scan returns a valid results object with detected Maven dependencies. + */ + @Test + @DisplayName("Basic OSS scan on pom.xml returns Maven dependencies") + void basicOssRealtimeScan() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + OssRealtimeResults results = wrapper.ossRealtimeScan("pom.xml", ""); + + assertNotNull(results, "Scan should return non-null results"); + assertFalse(results.getPackages().isEmpty(), "Should detect Maven dependencies in pom.xml"); + + // Verify each package has required fields populated + results.getPackages().forEach(pkg -> { + assertNotNull(pkg.getPackageName(), "Package name should be populated"); + assertNotNull(pkg.getStatus(), "Package status should be populated"); + }); + } + + /** + * Tests OSS scan with ignore file functionality. + * Verifies that providing an ignore file reduces or maintains the package count compared to baseline scan. + */ + @Test + @DisplayName("OSS scan with ignore file filters packages correctly") + void ossRealtimeScanWithIgnoreFile() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String ignoreFile = "src/test/resources/ignored-packages.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(ignoreFile)), "Ignore file not found - cannot test ignore functionality"); + + OssRealtimeResults baseline = wrapper.ossRealtimeScan("pom.xml", ""); + OssRealtimeResults filtered = wrapper.ossRealtimeScan("pom.xml", ignoreFile); + + assertNotNull(baseline, "Baseline scan should return results"); + assertNotNull(filtered, "Filtered scan should return results"); + assertTrue(filtered.getPackages().size() <= baseline.getPackages().size(), + "Filtered scan should have same or fewer packages than baseline"); + } + + /** + * Diagnostic test to see what package names are actually detected by the OSS scanner. + * This helps identify the correct package names for ignore file testing. + */ + @Test + @DisplayName("Display detected package names for diagnostic purposes") + void diagnosticPackageNames() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + OssRealtimeResults results = wrapper.ossRealtimeScan("pom.xml", ""); + assertFalse(results.getPackages().isEmpty(), "Should have packages for diagnostic"); + + // Print package names for debugging (will show in test output) + System.out.println("Detected package names:"); + results.getPackages().forEach(pkg -> + System.out.println(" - " + pkg.getPackageName() + " (Manager: " + pkg.getPackageManager() + ")") + ); + + // This test always passes - it's just for information gathering + assertTrue(true, "Diagnostic test completed"); + } + + /** + * Tests that specific packages listed in ignore file are actually excluded from scan results. + * Uses a more flexible approach to find packages that can be ignored. + */ + @Test + @DisplayName("Ignore file excludes detected packages correctly") + void ignoreFileExcludesPackages() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String ignoreFile = "src/test/resources/ignored-packages.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(ignoreFile)), "Ignore file not found - cannot test ignore functionality"); + + OssRealtimeResults baseline = wrapper.ossRealtimeScan("pom.xml", ""); + OssRealtimeResults filtered = wrapper.ossRealtimeScan("pom.xml", ignoreFile); + + // Look for common Maven packages that might be detected + String[] commonPackageNames = {"jackson-databind", "commons-lang3", "json-simple", "slf4j-simple", "junit-jupiter"}; + + boolean foundIgnoredPackage = false; + for (String packageName : commonPackageNames) { + boolean inBaseline = baseline.getPackages().stream() + .anyMatch(pkg -> packageName.equalsIgnoreCase(pkg.getPackageName())); + boolean inFiltered = filtered.getPackages().stream() + .anyMatch(pkg -> packageName.equalsIgnoreCase(pkg.getPackageName())); + + if (inBaseline && !inFiltered) { + foundIgnoredPackage = true; + System.out.println("Successfully filtered out package: " + packageName); + break; + } + } + assertTrue(filtered.getPackages().size() <= baseline.getPackages().size(), + "Filtered scan should not have more packages than baseline"); + } + + /** + * Tests scan consistency by running the same scan multiple times. + * Verifies that repeated scans of the same source produce stable, deterministic results. + */ + @Test + @DisplayName("Repeated OSS scans produce consistent results") + void ossRealtimeScanConsistency() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + OssRealtimeResults firstScan = wrapper.ossRealtimeScan("pom.xml", ""); + OssRealtimeResults secondScan = wrapper.ossRealtimeScan("pom.xml", ""); + + assertEquals(firstScan.getPackages().size(), secondScan.getPackages().size(), + "Package count should be consistent across multiple scans"); + } + + /** + * Tests domain object mapping by verifying all expected package fields are properly populated. + * Ensures the JSON to POJO conversion works correctly for all package attributes. + */ + @Test + @DisplayName("Package domain objects are properly mapped from scan results") + void packageDomainObjectMapping() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + OssRealtimeResults results = wrapper.ossRealtimeScan("pom.xml", ""); + assertFalse(results.getPackages().isEmpty(), "Should have packages to validate mapping"); + + OssRealtimeScanPackage samplePackage = results.getPackages().get(0); + + // Verify core package fields are mapped (some may be null based on scan results) + assertNotNull(samplePackage.getPackageName(), "Package name should always be present"); + assertNotNull(samplePackage.getStatus(), "Package status should always be present"); + assertNotNull(samplePackage.getLocations(), "Locations list should be initialized (may be empty)"); + assertNotNull(samplePackage.getVulnerabilities(), "Vulnerabilities list should be initialized (may be empty)"); + } +} diff --git a/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java new file mode 100644 index 00000000..a60680fe --- /dev/null +++ b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java @@ -0,0 +1,292 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.realtime.RealtimeLocation; +import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; +import com.checkmarx.ast.wrapper.CxException; +import org.junit.jupiter.api.*; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration and unit tests for Secrets Realtime scanner functionality. + * Tests the complete workflow: CLI invocation -> JSON parsing -> domain object mapping. + * Integration tests use python-vul-file.py as the scan target and are assumption-guarded for CI/local flexibility. + */ +class SecretsRealtimeResultsTest extends BaseTest { + + private boolean isCliConfigured() { + return Optional.ofNullable(getConfig().getPathToExecutable()).filter(s -> !s.isEmpty()).isPresent(); + } + + /* ------------------------------------------------------ */ + /* Integration tests for Secrets Realtime scanning */ + /* ------------------------------------------------------ */ + + /** + * Tests basic secrets realtime scan functionality on a vulnerable Python file. + * Verifies that the scan returns a valid results object and can detect hardcoded secrets + * such as passwords and credentials embedded in the source code. + */ + @Test + @DisplayName("Basic secrets scan on python file returns detected secrets") + void basicSecretsRealtimeScan() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String pythonFile = "src/test/resources/python-vul-file.py"; + Assumptions.assumeTrue(Files.exists(Paths.get(pythonFile)), "Python vulnerable file not found - cannot test secrets scanning"); + + SecretsRealtimeResults results = wrapper.secretsRealtimeScan(pythonFile, ""); + + assertNotNull(results, "Scan should return non-null results"); + assertNotNull(results.getSecrets(), "Secrets list should be initialized"); + + // The python file contains hardcoded credentials, so we expect some secrets to be found + if (!results.getSecrets().isEmpty()) { + results.getSecrets().forEach(secret -> { + assertNotNull(secret.getTitle(), "Secret title should be populated"); + assertNotNull(secret.getFilePath(), "Secret file path should be populated"); + assertNotNull(secret.getLocations(), "Secret locations should be initialized"); + }); + } + } + + /** + * Tests secrets scan with ignore file functionality. + * Verifies that providing an ignore file doesn't break the scanning process + * and produces consistent or reduced results compared to baseline scan. + */ + @Test + @DisplayName("Secrets scan with ignore file works correctly") + void secretsRealtimeScanWithIgnoreFile() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String pythonFile = "src/test/resources/python-vul-file.py"; + String ignoreFile = "src/test/resources/ignored-packages.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(pythonFile)) && Files.exists(Paths.get(ignoreFile)), + "Required test resources missing - cannot test ignore functionality"); + + SecretsRealtimeResults baseline = wrapper.secretsRealtimeScan(pythonFile, ""); + SecretsRealtimeResults filtered = wrapper.secretsRealtimeScan(pythonFile, ignoreFile); + + assertNotNull(baseline, "Baseline scan should return results"); + assertNotNull(filtered, "Filtered scan should return results"); + + // Ignore file should not increase the number of detected secrets + assertTrue(filtered.getSecrets().size() <= baseline.getSecrets().size(), + "Filtered scan should not have more secrets than baseline"); + } + + /** + * Tests scan consistency by running the same secrets scan multiple times. + * Verifies that repeated scans of the same file produce stable, deterministic results. + * This is crucial for ensuring reliable CI/CD pipeline integration. + */ + @Test + @DisplayName("Repeated secrets scans produce consistent results") + void secretsRealtimeScanConsistency() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String pythonFile = "src/test/resources/python-vul-file.py"; + Assumptions.assumeTrue(Files.exists(Paths.get(pythonFile)), "Python file not found - cannot test consistency"); + + SecretsRealtimeResults firstScan = wrapper.secretsRealtimeScan(pythonFile, ""); + SecretsRealtimeResults secondScan = wrapper.secretsRealtimeScan(pythonFile, ""); + + assertNotNull(firstScan, "First scan should return results"); + assertNotNull(secondScan, "Second scan should return results"); + + // Compare secret counts for consistency + assertEquals(firstScan.getSecrets().size(), secondScan.getSecrets().size(), + "Secret count should be consistent across multiple scans"); + } + + /** + * Tests domain object mapping for secrets scan results. + * Verifies that JSON responses are properly parsed into domain objects + * and all expected fields (title, description, severity, locations) are correctly mapped. + */ + @Test + @DisplayName("Secret domain objects are properly mapped from scan results") + void secretDomainObjectMapping() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String pythonFile = "src/test/resources/python-vul-file.py"; + Assumptions.assumeTrue(Files.exists(Paths.get(pythonFile)), "Python file not found - cannot test mapping"); + + SecretsRealtimeResults results = wrapper.secretsRealtimeScan(pythonFile, ""); + assertNotNull(results, "Scan results should not be null"); + + // If secrets are detected, validate their structure + if (!results.getSecrets().isEmpty()) { + SecretsRealtimeResults.Secret sampleSecret = results.getSecrets().get(0); + + // Verify core secret fields are mapped correctly + assertNotNull(sampleSecret.getTitle(), "Secret title should always be present"); + assertNotNull(sampleSecret.getFilePath(), "Secret file path should always be present"); + assertNotNull(sampleSecret.getLocations(), "Locations list should be initialized"); + + // Verify locations have proper structure if they exist + if (!sampleSecret.getLocations().isEmpty()) { + RealtimeLocation sampleLocation = sampleSecret.getLocations().get(0); + assertTrue(sampleLocation.getLine() > 0, "Line number should be positive"); + } + } + } + + /** + * Tests secrets scanning on a clean file that should not contain secrets. + * Verifies that the scanner correctly identifies files without secrets + * and returns empty results without errors. + */ + @Test + @DisplayName("Secrets scan on clean file returns empty results") + void secretsScanOnCleanFile() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String cleanFile = "src/test/resources/csharp-no-vul.cs"; + Assumptions.assumeTrue(Files.exists(Paths.get(cleanFile)), "Clean C# file not found - cannot test clean scan"); + + SecretsRealtimeResults results = wrapper.secretsRealtimeScan(cleanFile, ""); + assertNotNull(results, "Scan results should not be null even for clean files"); + + // Clean file should have no secrets or very few false positives + assertTrue(results.getSecrets().size() <= 2, + "Clean file should have no or minimal secrets detected"); + } + + /** + * Tests error handling when scanning a non-existent file. + * Verifies that the scanner properly throws a CxException with meaningful error message + * when provided with invalid file paths, demonstrating proper error handling. + */ + @Test + @DisplayName("Secrets scan throws appropriate exception for non-existent file") + void secretsScanHandlesInvalidPath() { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + // Test with a non-existent file path + String invalidPath = "src/test/resources/NonExistentFile.py"; + + // The CLI should throw a CxException with a meaningful error message for invalid paths + CxException exception = assertThrows(CxException.class, () -> + wrapper.secretsRealtimeScan(invalidPath, "") + ); + + // Verify the exception contains information about the invalid file path + String errorMessage = exception.getMessage(); + assertNotNull(errorMessage, "Exception should contain an error message"); + assertTrue(errorMessage.contains("invalid file path") || errorMessage.contains("file") || errorMessage.contains("path"), + "Exception message should indicate the issue is related to file path: " + errorMessage); + } + + /** + * Tests secrets scanning across multiple file types. + * Verifies that the scanner can handle different file extensions and formats + * without crashing and produces appropriate results for each file type. + */ + @Test + @DisplayName("Secrets scan handles multiple file types correctly") + void secretsScanMultipleFileTypes() { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + String[] testFiles = { + "src/test/resources/python-vul-file.py", + "src/test/resources/csharp-file.cs", + "src/test/resources/Dockerfile" + }; + + for (String filePath : testFiles) { + if (Files.exists(Paths.get(filePath))) { + assertDoesNotThrow(() -> { + SecretsRealtimeResults results = wrapper.secretsRealtimeScan(filePath, ""); + assertNotNull(results, "Results should not be null for file: " + filePath); + }, "Scanner should handle file type gracefully: " + filePath); + } + } + } + + /* ------------------------------------------------------ */ + /* Unit tests for JSON parsing robustness */ + /* ------------------------------------------------------ */ + + /** + * Tests JSON parsing with valid secrets scan response containing array format. + * Verifies that well-formed JSON arrays are correctly parsed into domain objects. + */ + @Test + @DisplayName("Valid JSON array parsing creates correct domain objects") + void testFromLineWithJsonArray() { + String json = "[" + + "{" + + "\"Title\":\"Hardcoded AWS Access Key\"," + + "\"Description\":\"An AWS access key is hardcoded in the source code. This is a security risk.\"," + + "\"SecretValue\":\"AKIAIOSFODNN7EXAMPLE\"," + + "\"FilePath\":\"/path/to/file.py\"," + + "\"Severity\":\"HIGH\"," + + "\"Locations\":[{\"StartLine\":10,\"StartColumn\":5,\"EndLine\":10,\"EndColumn\":25}]" + + "}" + + "]"; + SecretsRealtimeResults results = SecretsRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getSecrets().size()); + SecretsRealtimeResults.Secret secret = results.getSecrets().get(0); + assertEquals("Hardcoded AWS Access Key", secret.getTitle()); + assertEquals("An AWS access key is hardcoded in the source code. This is a security risk.", secret.getDescription()); + assertEquals("AKIAIOSFODNN7EXAMPLE", secret.getSecretValue()); + assertEquals("/path/to/file.py", secret.getFilePath()); + assertEquals("HIGH", secret.getSeverity()); + assertEquals(1, secret.getLocations().size()); + } + + /** + * Tests JSON parsing with valid secrets scan response containing single object format. + * Verifies that single JSON objects are correctly parsed into domain objects. + */ + @Test + @DisplayName("Valid JSON object parsing creates correct domain objects") + void testFromLineWithJsonObject() { + String json = "{" + + "\"Title\":\"Hardcoded AWS Access Key\"," + + "\"Description\":\"An AWS access key is hardcoded in the source code. This is a security risk.\"," + + "\"SecretValue\":\"AKIAIOSFODNN7EXAMPLE\"," + + "\"FilePath\":\"/path/to/file.py\"," + + "\"Severity\":\"HIGH\"," + + "\"Locations\":[{\"StartLine\":10,\"StartColumn\":5,\"EndLine\":10,\"EndColumn\":25}]" + + "}"; + SecretsRealtimeResults results = SecretsRealtimeResults.fromLine(json); + assertNotNull(results); + assertEquals(1, results.getSecrets().size()); + SecretsRealtimeResults.Secret secret = results.getSecrets().get(0); + assertEquals("Hardcoded AWS Access Key", secret.getTitle()); + } + + /** + * Tests parsing robustness with malformed JSON and edge cases. + * Verifies that the parser gracefully handles various invalid input scenarios. + */ + @Test + @DisplayName("Malformed JSON and edge cases are handled gracefully") + void testFromLineWithEdgeCases() { + // Blank/null inputs + assertNull(SecretsRealtimeResults.fromLine("")); + assertNull(SecretsRealtimeResults.fromLine(" ")); + assertNull(SecretsRealtimeResults.fromLine(null)); + + // Invalid JSON structures + assertNull(SecretsRealtimeResults.fromLine("{")); + assertNull(SecretsRealtimeResults.fromLine("not a json")); + } + + /** + * Tests parsing with empty results. + * Verifies that empty JSON arrays are handled correctly and produce valid empty results. + */ + @Test + @DisplayName("Empty JSON arrays are handled correctly") + void testFromLineWithEmptyResults() { + String emptyJson = "[]"; + SecretsRealtimeResults results = SecretsRealtimeResults.fromLine(emptyJson); + assertNotNull(results); + assertTrue(results.getSecrets().isEmpty()); + } +} + diff --git a/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java b/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java deleted file mode 100644 index f2ab9b4c..00000000 --- a/src/test/java/com/checkmarx/ast/unit/OssRealtimeParsingTest.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.checkmarx.ast.unit; - -import com.checkmarx.ast.ossrealtime.OssRealtimeResults; -import com.checkmarx.ast.ossrealtime.OssRealtimeScanPackage; -import com.checkmarx.ast.ossrealtime.OssRealtimeVulnerability; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.List; - -/** - * Unit tests for OssRealtimeResults JSON parsing and object construction. - * Focus: JSON parsing branches, constructor defaults, null/empty handling. - */ -class OssRealtimeParsingTest { - - /** Packages value is a string -> parser should fail and return null. */ - @Test - void fromLine_PackagesStringType_ReturnsNull() { - String json = "{\"Packages\":\"oops\"}"; - Assertions.assertNull(OssRealtimeResults.fromLine(json)); - } - - /** Packages array contains non-object entries -> parsing should fail and return null. */ - @Test - void fromLine_PackagesArrayWithNonObjectEntries_ReturnsNull() { - String json = "{\"Packages\":[123,\"abc\"]}"; - Assertions.assertNull(OssRealtimeResults.fromLine(json)); - } - - /** Packages key absent entirely -> fromLine returns null. */ - @Test - void fromLine_LineDoesNotContainPackagesKey_ReturnsNull() { - String jsonLine = "{\"PackageManager\":\"npm\"}"; // No top-level "Packages" key - Assertions.assertNull(OssRealtimeResults.fromLine(jsonLine)); - } - - /** Truncated JSON containing Packages key -> parse exception caught, returns null. */ - @Test - void fromLine_TruncatedJsonWithPackagesKey_CatchesAndReturnsNull() { - String truncated = "{\"Packages\":[{"; // contains "Packages" but invalid JSON - Assertions.assertNull(OssRealtimeResults.fromLine(truncated)); - } - - /** Packages key present but explicitly null -> results object with empty list. */ - @Test - void fromLine_PackagesNull_YieldsEmptyList() { - String json = "{\"Packages\":null}"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - Assertions.assertTrue(results.getPackages().isEmpty()); - } - - /** Empty Packages array -> empty list returned. */ - @Test - void fromLine_EmptyPackagesArray() { - String json = "{\"Packages\":[]}"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - Assertions.assertTrue(results.getPackages().isEmpty()); - } - - /** Package missing optional fields: packageManager & packageVersion should map to null. */ - @Test - void parsePackageMissingFields_AllowsNulls() { - String json = "{ \"Packages\": [{ \"PackageName\": \"only-name\", \"FilePath\": \"package.json\", \"Status\": \"OK\" }] }"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - Assertions.assertEquals(1, results.getPackages().size()); - OssRealtimeScanPackage pkg = results.getPackages().get(0); - Assertions.assertNull(pkg.getPackageManager()); - Assertions.assertEquals("only-name", pkg.getPackageName()); - Assertions.assertNull(pkg.getPackageVersion()); - } - - /** Unicode characters preserved in Description field. */ - @Test - void parseUnicodeInDescription() { - String json = "{ \"Packages\": [{ \"PackageManager\": \"npm\", \"PackageName\": \"u\", \"PackageVersion\": \"1\", \"FilePath\": \"p.json\", \"Status\": \"OK\", \"Vulnerabilities\": [{ \"Id\": \"CVE-u\", \"Severity\": \"Low\", \"Description\": \"Unicode snow ☃ and emoji 🚀\" }] }] }"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - Assertions.assertEquals(1, results.getPackages().size()); - OssRealtimeVulnerability vul = results.getPackages().get(0).getVulnerabilities().get(0); - Assertions.assertEquals("Unicode snow ☃ and emoji 🚀", vul.getDescription()); - Assertions.assertEquals("CVE-u", vul.getId()); - } - - /** Multiple vulnerabilities: one without fixVersion, one with fixVersion. */ - @Test - void parseMultipleVulnerabilities() { - String json = "{\n" + - " \"Packages\": [{\n" + - " \"PackageManager\": \"npm\",\n" + - " \"PackageName\": \"dep\",\n" + - " \"PackageVersion\": \"1.0.0\",\n" + - " \"FilePath\": \"/a/package.json\",\n" + - " \"Status\": \"OK\",\n" + - " \"Vulnerabilities\": [\n" + - " { \"Id\": \"CVE-1\", \"Severity\": \"Low\", \"Description\": \"d1\" },\n" + - " { \"Id\": \"CVE-2\", \"Severity\": \"Critical\", \"Description\": \"d2\", \"FixVersion\": \"2.0.0\" }\n" + - " ]\n" + - " }]\n" + - "}"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - OssRealtimeScanPackage pkg = results.getPackages().get(0); - List vulns = pkg.getVulnerabilities(); - Assertions.assertEquals(2, vulns.size()); - Assertions.assertEquals("CVE-1", vulns.get(0).getId()); - Assertions.assertNull(vulns.get(0).getFixVersion()); - Assertions.assertEquals("CVE-2", vulns.get(1).getId()); - Assertions.assertEquals("2.0.0", vulns.get(1).getFixVersion()); - } - - /** Explicit null lists should be normalized to empty lists by constructor. */ - @Test - void constructor_DefaultsEmptyListsWhenNull() throws IOException { - String json = "{\n" + - " \"Packages\": [{\n" + - " \"PackageManager\": \"pip\",\n" + - " \"PackageName\": \"requests\",\n" + - " \"PackageVersion\": \"2.0.0\",\n" + - " \"FilePath\": \"requirements.txt\",\n" + - " \"Status\": \"Unknown\",\n" + - " \"Locations\": null,\n" + - " \"Vulnerabilities\": null\n" + - " }]\n" + - "}"; - OssRealtimeResults results = new ObjectMapper().readValue(json, OssRealtimeResults.class); - Assertions.assertNotNull(results); - Assertions.assertEquals(1, results.getPackages().size()); - OssRealtimeScanPackage pkg = results.getPackages().get(0); - Assertions.assertTrue(pkg.getLocations().isEmpty()); - Assertions.assertTrue(pkg.getVulnerabilities().isEmpty()); - } - - /** All vulnerability fields mapped including fixVersion. */ - @Test - void vulnerability_AllFieldsMapped() { - String json = "{\n" + - " \"Packages\": [{\n" + - " \"PackageManager\": \"npm\",\n" + - " \"PackageName\": \"chalk\",\n" + - " \"PackageVersion\": \"5.0.0\",\n" + - " \"FilePath\": \"/w/package.json\",\n" + - " \"Status\": \"OK\",\n" + - " \"Vulnerabilities\": [{\n" + - " \"Id\": \"CVE-2025-9999\",\n" + - " \"Severity\": \"Medium\",\n" + - " \"Description\": \"Some issue\",\n" + - " \"FixVersion\": \"5.0.1\"\n" + - " }]\n" + - " }]\n" + - "}"; - OssRealtimeResults results = OssRealtimeResults.fromLine(json); - Assertions.assertNotNull(results); - OssRealtimeVulnerability vul = results.getPackages().get(0).getVulnerabilities().get(0); - Assertions.assertEquals("CVE-2025-9999", vul.getId()); - Assertions.assertEquals("Medium", vul.getSeverity()); - Assertions.assertEquals("Some issue", vul.getDescription()); - Assertions.assertEquals("5.0.1", vul.getFixVersion()); - } -} diff --git a/src/test/resources/ignored-packages.json b/src/test/resources/ignored-packages.json index 2ebbedc2..5ec153d5 100644 --- a/src/test/resources/ignored-packages.json +++ b/src/test/resources/ignored-packages.json @@ -1 +1,5 @@ -[{"name": "marked", "version": "*"}] \ No newline at end of file +[ + {"name": "jackson-databind", "version": "*"}, + {"name": "commons-lang3", "version": "*"}, + {"name": "json-simple", "version": "*"} +] From ee4c90cd577a645102310f58c1426b1bb93235fa Mon Sep 17 00:00:00 2001 From: cx-anand-nandeshwar <73646287+cx-anand-nandeshwar@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:17:35 +0530 Subject: [PATCH 19/37] Changed variable from id to CVE as per OSS response --- .../checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java index 7ac9c574..ac89b368 100644 --- a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java +++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.java @@ -12,17 +12,17 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class OssRealtimeVulnerability { - @JsonProperty("Id") String id; + @JsonProperty("CVE") String cve; @JsonProperty("Severity") String severity; @JsonProperty("Description") String description; @JsonProperty("FixVersion") String fixVersion; @JsonCreator - public OssRealtimeVulnerability(@JsonProperty("Id") String id, + public OssRealtimeVulnerability(@JsonProperty("CVE") String cve, @JsonProperty("Severity") String severity, @JsonProperty("Description") String description, @JsonProperty("FixVersion") String fixVersion) { - this.id = id; + this.cve = cve; this.severity = severity; this.description = description; this.fixVersion = fixVersion; From 97c6c699cb43ae54e74bb8880b2f76b0be18d0b3 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:00:02 +0530 Subject: [PATCH 20/37] Add maskedResult for secret remediation and change log level from INFO to DEBUG --- .../ast/secretsrealtime/MaskResult.java | 96 +++++++++ .../ast/secretsrealtime/MaskedSecret.java | 44 +++++ .../checkmarx/ast/wrapper/CxConstants.java | 1 + .../com/checkmarx/ast/wrapper/CxWrapper.java | 18 ++ .../com/checkmarx/ast/wrapper/Execution.java | 4 +- .../ast/SecretsRealtimeResultsTest.java | 184 ++++++++++++++++++ src/test/resources/secrets-test.json | 11 ++ 7 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/checkmarx/ast/secretsrealtime/MaskResult.java create mode 100644 src/main/java/com/checkmarx/ast/secretsrealtime/MaskedSecret.java create mode 100644 src/test/resources/secrets-test.json diff --git a/src/main/java/com/checkmarx/ast/secretsrealtime/MaskResult.java b/src/main/java/com/checkmarx/ast/secretsrealtime/MaskResult.java new file mode 100644 index 00000000..b39beb2e --- /dev/null +++ b/src/main/java/com/checkmarx/ast/secretsrealtime/MaskResult.java @@ -0,0 +1,96 @@ +package com.checkmarx.ast.secretsrealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents the result of a mask secrets command operation. + * Contains masked secrets and the masked file content. + * This is separate from realtime scanning results. + */ +@Value +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MaskResult { + private static final Logger log = LoggerFactory.getLogger(MaskResult.class); + + /** + * List of masked secrets found in the file + */ + @JsonProperty("maskedSecrets") + List maskedSecrets; + + /** + * The masked file content with secrets redacted + */ + @JsonProperty("maskedFile") + String maskedFile; + + @JsonCreator + public MaskResult(@JsonProperty("maskedSecrets") List maskedSecrets, + @JsonProperty("maskedFile") String maskedFile) { + this.maskedSecrets = maskedSecrets == null ? Collections.emptyList() : maskedSecrets; + this.maskedFile = maskedFile; + } + + /** + * Parses mask command output from JSON response + * @param root JsonNode containing the mask command response + * @return MaskResult object with parsed data + */ + public static MaskResult parse(JsonNode root) { + if (root == null) { + return new MaskResult(Collections.emptyList(), ""); + } + + List secrets = new ArrayList<>(); + JsonNode maskedSecretsNode = root.get("maskedSecrets"); + + if (maskedSecretsNode != null && maskedSecretsNode.isArray()) { + for (JsonNode secretNode : maskedSecretsNode) { + String masked = secretNode.has("masked") ? secretNode.get("masked").asText() : ""; + String secret = secretNode.has("secret") ? secretNode.get("secret").asText() : ""; + int line = secretNode.has("line") ? secretNode.get("line").asInt() : 0; + + secrets.add(new MaskedSecret(masked, secret, line)); + } + } + + String maskedFile = root.has("maskedFile") ? root.get("maskedFile").asText() : ""; + + return new MaskResult(secrets, maskedFile); + } + + /** + * Parses mask command output from JSON string + * @param jsonString JSON string containing the mask command response + * @return MaskResult object with parsed data, or null if parsing fails + */ + public static MaskResult fromJsonString(String jsonString) { + if (StringUtils.isBlank(jsonString)) { + return null; + } + + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(jsonString.trim()); + return parse(root); + } catch (IOException e) { + log.debug("Failed to parse mask result JSON: {}", jsonString, e); + return null; + } + } +} diff --git a/src/main/java/com/checkmarx/ast/secretsrealtime/MaskedSecret.java b/src/main/java/com/checkmarx/ast/secretsrealtime/MaskedSecret.java new file mode 100644 index 00000000..f78b950c --- /dev/null +++ b/src/main/java/com/checkmarx/ast/secretsrealtime/MaskedSecret.java @@ -0,0 +1,44 @@ +package com.checkmarx.ast.secretsrealtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +/** + * Represents a single masked secret from the mask command output. + * This is used for the separate mask functionality (not realtime scan results). + */ +@Value +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MaskedSecret { + + /** + * The masked/redacted version of the secret + */ + @JsonProperty("masked") + String masked; + + /** + * The original secret value (may be empty for security reasons) + */ + @JsonProperty("secret") + String secret; + + /** + * Line number where the secret was found + */ + @JsonProperty("line") + int line; + + @JsonCreator + public MaskedSecret(@JsonProperty("masked") String masked, + @JsonProperty("secret") String secret, + @JsonProperty("line") int line) { + this.masked = masked; + this.secret = secret; + this.line = line; + } +} diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index 57611763..8f80712c 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -80,4 +80,5 @@ public final class CxConstants { static final String SUB_CMD_IAC_REALTIME = "iac-realtime"; static final String SUB_CMD_SECRETS_REALTIME = "secrets-realtime"; static final String SUB_CMD_CONTAINERS_REALTIME = "containers-realtime"; + static final String CMD_MASK_SECRETS = "mask"; } diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 85be850d..f391f652 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -6,6 +6,7 @@ import com.checkmarx.ast.learnMore.LearnMore; import com.checkmarx.ast.ossrealtime.OssRealtimeResults; import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; +import com.checkmarx.ast.secretsrealtime.MaskResult; import com.checkmarx.ast.iacrealtime.IacRealtimeResults; import com.checkmarx.ast.containersrealtime.ContainersRealtimeResults; import com.checkmarx.ast.predicate.CustomState; @@ -441,6 +442,23 @@ public SecretsRealtimeResults secretsRealtimeScan(@NonNull String sourcePath, St return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, SecretsRealtimeResults::fromLine); } + /** + * Executes mask secrets command to obfuscate/redact secrets in a file + * @param filePath path to the file to mask + * @return MaskResult containing masked secrets and masked file content + */ + public MaskResult maskSecrets(@NonNull String filePath) throws IOException, InterruptedException, CxException { + this.logger.info("Executing 'mask' command using the CLI for file: {}", filePath); + + List arguments = new ArrayList<>(); + arguments.add(CxConstants.CMD_MASK_SECRETS); + arguments.add(CxConstants.SOURCE); + arguments.add(filePath); + + String output = Execution.executeCommand(withConfigArguments(arguments), logger, line -> line); + return MaskResult.fromJsonString(output); + } + // Containers Realtime public ContainersRealtimeResults containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { diff --git a/src/main/java/com/checkmarx/ast/wrapper/Execution.java b/src/main/java/com/checkmarx/ast/wrapper/Execution.java index 9d45cac3..c233ff2d 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/Execution.java +++ b/src/main/java/com/checkmarx/ast/wrapper/Execution.java @@ -57,7 +57,7 @@ static T executeCommand(List arguments, String line; StringBuilder output = new StringBuilder(); while ((line = br.readLine()) != null) { - logger.info(line); + logger.debug(line); output.append(line).append(LINE_SEPARATOR); T parsedLine = lineParser.apply(line); if (parsedLine != null) { @@ -98,7 +98,7 @@ static String executeCommand(List arguments, String line; StringBuilder stringBuilder = new StringBuilder(); while ((line = br.readLine()) != null) { - logger.info(line); + logger.debug(line); stringBuilder.append(line).append(LINE_SEPARATOR); } process.waitFor(); diff --git a/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java index a60680fe..a82e9e93 100644 --- a/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java +++ b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java @@ -2,6 +2,8 @@ import com.checkmarx.ast.realtime.RealtimeLocation; import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; +import com.checkmarx.ast.secretsrealtime.MaskResult; +import com.checkmarx.ast.secretsrealtime.MaskedSecret; import com.checkmarx.ast.wrapper.CxException; import org.junit.jupiter.api.*; @@ -204,6 +206,188 @@ void secretsScanMultipleFileTypes() { } } + /* ------------------------------------------------------ */ + /* Integration tests for Secrets Masking functionality */ + /* ------------------------------------------------------ */ + + /** + * Tests basic mask secrets functionality - successful case. + * Similar to the JavaScript test, verifies that the mask command returns proper MaskResult + * with masked secrets detected in a JSON file containing API keys and passwords. + */ + @Test + @DisplayName("Mask secrets successful case - returns masked content") + void maskSecretsSuccessfulCase() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String secretsFile = "src/test/resources/secrets-test.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(secretsFile)), "Secrets test file not found - cannot test masking"); + + MaskResult result = wrapper.maskSecrets(secretsFile); + + assertNotNull(result, "Mask result should not be null"); + assertNotNull(result.getMaskedSecrets(), "Masked secrets list should be initialized"); + assertNotNull(result.getMaskedFile(), "Masked file content should be provided"); + + // Expect at least one secret to be found in our test file + assertFalse(result.getMaskedSecrets().isEmpty(), "Should find masked secrets in test file"); + + // Verify structure of masked secrets + MaskedSecret firstSecret = result.getMaskedSecrets().get(0); + assertNotNull(firstSecret.getMasked(), "Masked value should be provided"); + assertTrue(firstSecret.getLine() > 0, "Line number should be positive"); + + // Masked file should contain the original structure but with secrets redacted + assertFalse(result.getMaskedFile().trim().isEmpty(), "Masked file content should not be empty"); + assertTrue(result.getMaskedFile().contains("{"), "Masked file should preserve JSON structure"); + } + + /** + * Tests mask functionality across different file types. + * Verifies that the mask command can handle various file extensions and formats + * without crashing and produces appropriate masked results. + */ + @Test + @DisplayName("Mask secrets handles multiple file types correctly") + void maskSecretsMultipleFileTypes() { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + String[] testFiles = { + "src/test/resources/python-vul-file.py", + "src/test/resources/csharp-file.cs" + }; + + for (String filePath : testFiles) { + if (Files.exists(Paths.get(filePath))) { + assertDoesNotThrow(() -> { + MaskResult result = wrapper.maskSecrets(filePath); + assertNotNull(result, "Mask result should not be null for file: " + filePath); + assertNotNull(result.getMaskedSecrets(), "Masked secrets should be initialized for: " + filePath); + assertNotNull(result.getMaskedFile(), "Masked file should not be null for: " + filePath); + }, "Mask command should handle file type gracefully: " + filePath); + } + } + } + + /** + * Tests error handling when masking a non-existent file. + * Verifies that the mask command properly throws a CxException with meaningful error message + * when provided with invalid file paths. + */ + @Test + @DisplayName("Mask secrets throws appropriate exception for non-existent file") + void maskSecretsHandlesInvalidPath() { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + + // Test with a non-existent file path + String invalidPath = "src/test/resources/NonExistentFile.py"; + + // The CLI should throw a CxException with a meaningful error message for invalid paths + CxException exception = assertThrows(CxException.class, () -> + wrapper.maskSecrets(invalidPath) + ); + + // Verify the exception contains information about the invalid file path + String errorMessage = exception.getMessage(); + assertNotNull(errorMessage, "Exception should contain an error message"); + assertTrue(errorMessage.contains("invalid file path") || errorMessage.contains("file") || errorMessage.contains("path"), + "Exception message should indicate the issue is related to file path: " + errorMessage); + } + + /** + * Tests that masked file content differs from original when secrets are present. + * Verifies that the masking process actually modifies the file content to redact secrets. + */ + @Test + @DisplayName("Masked file content differs from original when secrets exist") + void maskedContentDiffersFromOriginal() throws Exception { + Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); + String secretsFile = "src/test/resources/secrets-test.json"; + Assumptions.assumeTrue(Files.exists(Paths.get(secretsFile)), "Secrets test file not found - cannot test content masking"); + + // Read original file content + String originalContent = Files.readString(Paths.get(secretsFile)); + + // Get masked content + MaskResult result = wrapper.maskSecrets(secretsFile); + assertNotNull(result, "Mask result should not be null"); + + String maskedContent = result.getMaskedFile(); + assertNotNull(maskedContent, "Masked content should not be null"); + + // Since our test file contains secrets, the content should be different after masking + if (!result.getMaskedSecrets().isEmpty()) { + assertNotEquals(originalContent, maskedContent, + "Masked content should differ from original when secrets are present"); + + // Verify that original secrets are not present in masked content + assertFalse(maskedContent.contains("sk-1234567890abcdef1234567890abcdef"), + "Original API key should be masked in output"); + assertFalse(maskedContent.contains("SuperSecret123!"), + "Original password should be masked in output"); + } + } + + /* ------------------------------------------------------ */ + /* Unit tests for Mask JSON parsing functionality */ + /* ------------------------------------------------------ */ + + /** + * Tests MaskResult JSON parsing with valid mask command response. + * Verifies that well-formed mask JSON is correctly parsed into MaskResult objects. + */ + @Test + @DisplayName("Valid mask JSON response parsing creates correct MaskResult") + void testMaskResultJsonParsing() { + String json = "{" + + "\"maskedSecrets\":[" + + "{\"masked\":\"****\",\"secret\":\"password123\",\"line\":5}," + + "{\"masked\":\"***\",\"secret\":\"key\",\"line\":10}" + + "]," + + "\"maskedFile\":\"const password = '****';\\nconst apiKey = '***';\"" + + "}"; + + MaskResult result = MaskResult.fromJsonString(json); + + assertNotNull(result, "MaskResult should not be null"); + assertEquals(2, result.getMaskedSecrets().size(), "Should parse 2 masked secrets"); + + MaskedSecret firstSecret = result.getMaskedSecrets().get(0); + assertEquals("****", firstSecret.getMasked()); + assertEquals("password123", firstSecret.getSecret()); + assertEquals(5, firstSecret.getLine()); + + MaskedSecret secondSecret = result.getMaskedSecrets().get(1); + assertEquals("***", secondSecret.getMasked()); + assertEquals("key", secondSecret.getSecret()); + assertEquals(10, secondSecret.getLine()); + + assertTrue(result.getMaskedFile().contains("const password = '****'")); + assertTrue(result.getMaskedFile().contains("const apiKey = '***'")); + } + + /** + * Tests MaskResult parsing robustness with edge cases. + * Verifies that the parser gracefully handles various invalid input scenarios. + */ + @Test + @DisplayName("MaskResult handles malformed JSON and edge cases gracefully") + void testMaskResultEdgeCases() { + // Blank/null inputs + assertNull(MaskResult.fromJsonString("")); + assertNull(MaskResult.fromJsonString(" ")); + assertNull(MaskResult.fromJsonString(null)); + + // Invalid JSON structures + assertNull(MaskResult.fromJsonString("{")); + assertNull(MaskResult.fromJsonString("not a json")); + + // Empty but valid JSON + MaskResult emptyResult = MaskResult.fromJsonString("{}"); + assertNotNull(emptyResult); + assertTrue(emptyResult.getMaskedSecrets().isEmpty()); + assertNotNull(emptyResult.getMaskedFile()); + } + /* ------------------------------------------------------ */ /* Unit tests for JSON parsing robustness */ /* ------------------------------------------------------ */ diff --git a/src/test/resources/secrets-test.json b/src/test/resources/secrets-test.json new file mode 100644 index 00000000..a19351ed --- /dev/null +++ b/src/test/resources/secrets-test.json @@ -0,0 +1,11 @@ +{ + "api_key": "sk-1234567890abcdef1234567890abcdef", + "database_password": "SuperSecret123!", + "aws_access_key": "AKIAIOSFODNN7EXAMPLE", + "github_token": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "config": { + "secret": "my-secret-value-2023", + "connection_string": "Server=localhost;Database=test;User Id=sa;Password=P@ssw0rd;" + } +} + From 82d6c666841dcff72f464ebb9622a7b6145209ff Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:43:52 +0530 Subject: [PATCH 21/37] Remove masked secrets functionality from codebase --- .../ast/secretsrealtime/MaskResult.java | 96 --------- .../ast/secretsrealtime/MaskedSecret.java | 44 ----- .../checkmarx/ast/wrapper/CxConstants.java | 1 - .../com/checkmarx/ast/wrapper/CxWrapper.java | 17 +- .../ast/SecretsRealtimeResultsTest.java | 183 ------------------ src/test/resources/secrets-test.json | 11 -- 6 files changed, 1 insertion(+), 351 deletions(-) delete mode 100644 src/main/java/com/checkmarx/ast/secretsrealtime/MaskResult.java delete mode 100644 src/main/java/com/checkmarx/ast/secretsrealtime/MaskedSecret.java delete mode 100644 src/test/resources/secrets-test.json diff --git a/src/main/java/com/checkmarx/ast/secretsrealtime/MaskResult.java b/src/main/java/com/checkmarx/ast/secretsrealtime/MaskResult.java deleted file mode 100644 index b39beb2e..00000000 --- a/src/main/java/com/checkmarx/ast/secretsrealtime/MaskResult.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.checkmarx.ast.secretsrealtime; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Value; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Represents the result of a mask secrets command operation. - * Contains masked secrets and the masked file content. - * This is separate from realtime scanning results. - */ -@Value -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public class MaskResult { - private static final Logger log = LoggerFactory.getLogger(MaskResult.class); - - /** - * List of masked secrets found in the file - */ - @JsonProperty("maskedSecrets") - List maskedSecrets; - - /** - * The masked file content with secrets redacted - */ - @JsonProperty("maskedFile") - String maskedFile; - - @JsonCreator - public MaskResult(@JsonProperty("maskedSecrets") List maskedSecrets, - @JsonProperty("maskedFile") String maskedFile) { - this.maskedSecrets = maskedSecrets == null ? Collections.emptyList() : maskedSecrets; - this.maskedFile = maskedFile; - } - - /** - * Parses mask command output from JSON response - * @param root JsonNode containing the mask command response - * @return MaskResult object with parsed data - */ - public static MaskResult parse(JsonNode root) { - if (root == null) { - return new MaskResult(Collections.emptyList(), ""); - } - - List secrets = new ArrayList<>(); - JsonNode maskedSecretsNode = root.get("maskedSecrets"); - - if (maskedSecretsNode != null && maskedSecretsNode.isArray()) { - for (JsonNode secretNode : maskedSecretsNode) { - String masked = secretNode.has("masked") ? secretNode.get("masked").asText() : ""; - String secret = secretNode.has("secret") ? secretNode.get("secret").asText() : ""; - int line = secretNode.has("line") ? secretNode.get("line").asInt() : 0; - - secrets.add(new MaskedSecret(masked, secret, line)); - } - } - - String maskedFile = root.has("maskedFile") ? root.get("maskedFile").asText() : ""; - - return new MaskResult(secrets, maskedFile); - } - - /** - * Parses mask command output from JSON string - * @param jsonString JSON string containing the mask command response - * @return MaskResult object with parsed data, or null if parsing fails - */ - public static MaskResult fromJsonString(String jsonString) { - if (StringUtils.isBlank(jsonString)) { - return null; - } - - try { - ObjectMapper mapper = new ObjectMapper(); - JsonNode root = mapper.readTree(jsonString.trim()); - return parse(root); - } catch (IOException e) { - log.debug("Failed to parse mask result JSON: {}", jsonString, e); - return null; - } - } -} diff --git a/src/main/java/com/checkmarx/ast/secretsrealtime/MaskedSecret.java b/src/main/java/com/checkmarx/ast/secretsrealtime/MaskedSecret.java deleted file mode 100644 index f78b950c..00000000 --- a/src/main/java/com/checkmarx/ast/secretsrealtime/MaskedSecret.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.checkmarx.ast.secretsrealtime; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Value; - -/** - * Represents a single masked secret from the mask command output. - * This is used for the separate mask functionality (not realtime scan results). - */ -@Value -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public class MaskedSecret { - - /** - * The masked/redacted version of the secret - */ - @JsonProperty("masked") - String masked; - - /** - * The original secret value (may be empty for security reasons) - */ - @JsonProperty("secret") - String secret; - - /** - * Line number where the secret was found - */ - @JsonProperty("line") - int line; - - @JsonCreator - public MaskedSecret(@JsonProperty("masked") String masked, - @JsonProperty("secret") String secret, - @JsonProperty("line") int line) { - this.masked = masked; - this.secret = secret; - this.line = line; - } -} diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index 8f80712c..57611763 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -80,5 +80,4 @@ public final class CxConstants { static final String SUB_CMD_IAC_REALTIME = "iac-realtime"; static final String SUB_CMD_SECRETS_REALTIME = "secrets-realtime"; static final String SUB_CMD_CONTAINERS_REALTIME = "containers-realtime"; - static final String CMD_MASK_SECRETS = "mask"; } diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index f391f652..4d443c72 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -6,7 +6,7 @@ import com.checkmarx.ast.learnMore.LearnMore; import com.checkmarx.ast.ossrealtime.OssRealtimeResults; import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; -import com.checkmarx.ast.secretsrealtime.MaskResult; + import com.checkmarx.ast.iacrealtime.IacRealtimeResults; import com.checkmarx.ast.containersrealtime.ContainersRealtimeResults; import com.checkmarx.ast.predicate.CustomState; @@ -442,22 +442,7 @@ public SecretsRealtimeResults secretsRealtimeScan(@NonNull String sourcePath, St return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, SecretsRealtimeResults::fromLine); } - /** - * Executes mask secrets command to obfuscate/redact secrets in a file - * @param filePath path to the file to mask - * @return MaskResult containing masked secrets and masked file content - */ - public MaskResult maskSecrets(@NonNull String filePath) throws IOException, InterruptedException, CxException { - this.logger.info("Executing 'mask' command using the CLI for file: {}", filePath); - List arguments = new ArrayList<>(); - arguments.add(CxConstants.CMD_MASK_SECRETS); - arguments.add(CxConstants.SOURCE); - arguments.add(filePath); - - String output = Execution.executeCommand(withConfigArguments(arguments), logger, line -> line); - return MaskResult.fromJsonString(output); - } // Containers Realtime public ContainersRealtimeResults containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) diff --git a/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java index a82e9e93..a662d0f1 100644 --- a/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java +++ b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java @@ -2,8 +2,6 @@ import com.checkmarx.ast.realtime.RealtimeLocation; import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; -import com.checkmarx.ast.secretsrealtime.MaskResult; -import com.checkmarx.ast.secretsrealtime.MaskedSecret; import com.checkmarx.ast.wrapper.CxException; import org.junit.jupiter.api.*; @@ -206,187 +204,6 @@ void secretsScanMultipleFileTypes() { } } - /* ------------------------------------------------------ */ - /* Integration tests for Secrets Masking functionality */ - /* ------------------------------------------------------ */ - - /** - * Tests basic mask secrets functionality - successful case. - * Similar to the JavaScript test, verifies that the mask command returns proper MaskResult - * with masked secrets detected in a JSON file containing API keys and passwords. - */ - @Test - @DisplayName("Mask secrets successful case - returns masked content") - void maskSecretsSuccessfulCase() throws Exception { - Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); - String secretsFile = "src/test/resources/secrets-test.json"; - Assumptions.assumeTrue(Files.exists(Paths.get(secretsFile)), "Secrets test file not found - cannot test masking"); - - MaskResult result = wrapper.maskSecrets(secretsFile); - - assertNotNull(result, "Mask result should not be null"); - assertNotNull(result.getMaskedSecrets(), "Masked secrets list should be initialized"); - assertNotNull(result.getMaskedFile(), "Masked file content should be provided"); - - // Expect at least one secret to be found in our test file - assertFalse(result.getMaskedSecrets().isEmpty(), "Should find masked secrets in test file"); - - // Verify structure of masked secrets - MaskedSecret firstSecret = result.getMaskedSecrets().get(0); - assertNotNull(firstSecret.getMasked(), "Masked value should be provided"); - assertTrue(firstSecret.getLine() > 0, "Line number should be positive"); - - // Masked file should contain the original structure but with secrets redacted - assertFalse(result.getMaskedFile().trim().isEmpty(), "Masked file content should not be empty"); - assertTrue(result.getMaskedFile().contains("{"), "Masked file should preserve JSON structure"); - } - - /** - * Tests mask functionality across different file types. - * Verifies that the mask command can handle various file extensions and formats - * without crashing and produces appropriate masked results. - */ - @Test - @DisplayName("Mask secrets handles multiple file types correctly") - void maskSecretsMultipleFileTypes() { - Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); - - String[] testFiles = { - "src/test/resources/python-vul-file.py", - "src/test/resources/csharp-file.cs" - }; - - for (String filePath : testFiles) { - if (Files.exists(Paths.get(filePath))) { - assertDoesNotThrow(() -> { - MaskResult result = wrapper.maskSecrets(filePath); - assertNotNull(result, "Mask result should not be null for file: " + filePath); - assertNotNull(result.getMaskedSecrets(), "Masked secrets should be initialized for: " + filePath); - assertNotNull(result.getMaskedFile(), "Masked file should not be null for: " + filePath); - }, "Mask command should handle file type gracefully: " + filePath); - } - } - } - - /** - * Tests error handling when masking a non-existent file. - * Verifies that the mask command properly throws a CxException with meaningful error message - * when provided with invalid file paths. - */ - @Test - @DisplayName("Mask secrets throws appropriate exception for non-existent file") - void maskSecretsHandlesInvalidPath() { - Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); - - // Test with a non-existent file path - String invalidPath = "src/test/resources/NonExistentFile.py"; - - // The CLI should throw a CxException with a meaningful error message for invalid paths - CxException exception = assertThrows(CxException.class, () -> - wrapper.maskSecrets(invalidPath) - ); - - // Verify the exception contains information about the invalid file path - String errorMessage = exception.getMessage(); - assertNotNull(errorMessage, "Exception should contain an error message"); - assertTrue(errorMessage.contains("invalid file path") || errorMessage.contains("file") || errorMessage.contains("path"), - "Exception message should indicate the issue is related to file path: " + errorMessage); - } - - /** - * Tests that masked file content differs from original when secrets are present. - * Verifies that the masking process actually modifies the file content to redact secrets. - */ - @Test - @DisplayName("Masked file content differs from original when secrets exist") - void maskedContentDiffersFromOriginal() throws Exception { - Assumptions.assumeTrue(isCliConfigured(), "PATH_TO_EXECUTABLE not configured - skipping integration test"); - String secretsFile = "src/test/resources/secrets-test.json"; - Assumptions.assumeTrue(Files.exists(Paths.get(secretsFile)), "Secrets test file not found - cannot test content masking"); - - // Read original file content - String originalContent = Files.readString(Paths.get(secretsFile)); - - // Get masked content - MaskResult result = wrapper.maskSecrets(secretsFile); - assertNotNull(result, "Mask result should not be null"); - - String maskedContent = result.getMaskedFile(); - assertNotNull(maskedContent, "Masked content should not be null"); - - // Since our test file contains secrets, the content should be different after masking - if (!result.getMaskedSecrets().isEmpty()) { - assertNotEquals(originalContent, maskedContent, - "Masked content should differ from original when secrets are present"); - - // Verify that original secrets are not present in masked content - assertFalse(maskedContent.contains("sk-1234567890abcdef1234567890abcdef"), - "Original API key should be masked in output"); - assertFalse(maskedContent.contains("SuperSecret123!"), - "Original password should be masked in output"); - } - } - - /* ------------------------------------------------------ */ - /* Unit tests for Mask JSON parsing functionality */ - /* ------------------------------------------------------ */ - - /** - * Tests MaskResult JSON parsing with valid mask command response. - * Verifies that well-formed mask JSON is correctly parsed into MaskResult objects. - */ - @Test - @DisplayName("Valid mask JSON response parsing creates correct MaskResult") - void testMaskResultJsonParsing() { - String json = "{" + - "\"maskedSecrets\":[" + - "{\"masked\":\"****\",\"secret\":\"password123\",\"line\":5}," + - "{\"masked\":\"***\",\"secret\":\"key\",\"line\":10}" + - "]," + - "\"maskedFile\":\"const password = '****';\\nconst apiKey = '***';\"" + - "}"; - - MaskResult result = MaskResult.fromJsonString(json); - - assertNotNull(result, "MaskResult should not be null"); - assertEquals(2, result.getMaskedSecrets().size(), "Should parse 2 masked secrets"); - - MaskedSecret firstSecret = result.getMaskedSecrets().get(0); - assertEquals("****", firstSecret.getMasked()); - assertEquals("password123", firstSecret.getSecret()); - assertEquals(5, firstSecret.getLine()); - - MaskedSecret secondSecret = result.getMaskedSecrets().get(1); - assertEquals("***", secondSecret.getMasked()); - assertEquals("key", secondSecret.getSecret()); - assertEquals(10, secondSecret.getLine()); - - assertTrue(result.getMaskedFile().contains("const password = '****'")); - assertTrue(result.getMaskedFile().contains("const apiKey = '***'")); - } - - /** - * Tests MaskResult parsing robustness with edge cases. - * Verifies that the parser gracefully handles various invalid input scenarios. - */ - @Test - @DisplayName("MaskResult handles malformed JSON and edge cases gracefully") - void testMaskResultEdgeCases() { - // Blank/null inputs - assertNull(MaskResult.fromJsonString("")); - assertNull(MaskResult.fromJsonString(" ")); - assertNull(MaskResult.fromJsonString(null)); - - // Invalid JSON structures - assertNull(MaskResult.fromJsonString("{")); - assertNull(MaskResult.fromJsonString("not a json")); - - // Empty but valid JSON - MaskResult emptyResult = MaskResult.fromJsonString("{}"); - assertNotNull(emptyResult); - assertTrue(emptyResult.getMaskedSecrets().isEmpty()); - assertNotNull(emptyResult.getMaskedFile()); - } /* ------------------------------------------------------ */ /* Unit tests for JSON parsing robustness */ diff --git a/src/test/resources/secrets-test.json b/src/test/resources/secrets-test.json deleted file mode 100644 index a19351ed..00000000 --- a/src/test/resources/secrets-test.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "api_key": "sk-1234567890abcdef1234567890abcdef", - "database_password": "SuperSecret123!", - "aws_access_key": "AKIAIOSFODNN7EXAMPLE", - "github_token": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "config": { - "secret": "my-secret-value-2023", - "connection_string": "Server=localhost;Database=test;User Id=sa;Password=P@ssw0rd;" - } -} - From 23ddb2dfbb3d8a0b1b52f74287c965a7ae5f9591 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:26:50 +0530 Subject: [PATCH 22/37] Implemented mask cmd in java wrapper --- .../com/checkmarx/ast/mask/MaskResult.java | 37 +++++ .../com/checkmarx/ast/mask/MaskedSecret.java | 32 ++++ .../checkmarx/ast/wrapper/CxConstants.java | 2 + .../com/checkmarx/ast/wrapper/CxWrapper.java | 12 ++ src/test/java/com/checkmarx/ast/MaskTest.java | 105 +++++++++++++ src/test/resources/Secrets-realtime.json | 147 ++++++++++++++++++ 6 files changed, 335 insertions(+) create mode 100644 src/main/java/com/checkmarx/ast/mask/MaskResult.java create mode 100644 src/main/java/com/checkmarx/ast/mask/MaskedSecret.java create mode 100644 src/test/java/com/checkmarx/ast/MaskTest.java create mode 100644 src/test/resources/Secrets-realtime.json diff --git a/src/main/java/com/checkmarx/ast/mask/MaskResult.java b/src/main/java/com/checkmarx/ast/mask/MaskResult.java new file mode 100644 index 00000000..80d9e8bb --- /dev/null +++ b/src/main/java/com/checkmarx/ast/mask/MaskResult.java @@ -0,0 +1,37 @@ +package com.checkmarx.ast.mask; + +import com.checkmarx.ast.utils.JsonParser; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.type.TypeFactory; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; + +import java.util.List; + +@Value +@EqualsAndHashCode() +@JsonDeserialize() +@ToString(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MaskResult { + + List maskedSecrets; + String maskedFile; + + @JsonCreator + public MaskResult(@JsonProperty("maskedSecrets") List maskedSecrets, + @JsonProperty("maskedFile") String maskedFile) { + this.maskedSecrets = maskedSecrets; + this.maskedFile = maskedFile; + } + + public static MaskResult fromLine(String line) { + return JsonParser.parse(line, TypeFactory.defaultInstance().constructType(MaskResult.class)); + } +} diff --git a/src/main/java/com/checkmarx/ast/mask/MaskedSecret.java b/src/main/java/com/checkmarx/ast/mask/MaskedSecret.java new file mode 100644 index 00000000..a18e8827 --- /dev/null +++ b/src/main/java/com/checkmarx/ast/mask/MaskedSecret.java @@ -0,0 +1,32 @@ +package com.checkmarx.ast.mask; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; + +@Value +@EqualsAndHashCode() +@JsonDeserialize() +@ToString(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MaskedSecret { + + String masked; + String secret; + int line; + + @JsonCreator + public MaskedSecret(@JsonProperty("masked") String masked, + @JsonProperty("secret") String secret, + @JsonProperty("line") int line) { + this.masked = masked; + this.secret = secret; + this.line = line; + } +} diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index 57611763..dd8c6e69 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -80,4 +80,6 @@ public final class CxConstants { static final String SUB_CMD_IAC_REALTIME = "iac-realtime"; static final String SUB_CMD_SECRETS_REALTIME = "secrets-realtime"; static final String SUB_CMD_CONTAINERS_REALTIME = "containers-realtime"; + static final String SUB_CMD_MASK = "mask"; + static final String RESULT_FILE = "--result-file"; } diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 4d443c72..c49aa875 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -4,6 +4,7 @@ import com.checkmarx.ast.codebashing.CodeBashing; import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; import com.checkmarx.ast.learnMore.LearnMore; +import com.checkmarx.ast.mask.MaskResult; import com.checkmarx.ast.ossrealtime.OssRealtimeResults; import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults; @@ -522,6 +523,17 @@ public List tenantSettings() throws CxException, IOException, Int return Execution.executeCommand(withConfigArguments(arguments), logger, TenantSetting::listFromLine); } + public MaskResult maskSecrets(@NonNull String filePath) throws CxException, IOException, InterruptedException { + List arguments = new ArrayList<>(); + + arguments.add(CxConstants.CMD_UTILS); + arguments.add(CxConstants.SUB_CMD_MASK); + arguments.add(CxConstants.RESULT_FILE); + arguments.add(filePath); + + return Execution.executeCommand(withConfigArguments(arguments), logger, MaskResult::fromLine); + } + private int getIndexOfBfLNode(List bflNodes, List resultNodes) { int bflNodeNotFound = -1; diff --git a/src/test/java/com/checkmarx/ast/MaskTest.java b/src/test/java/com/checkmarx/ast/MaskTest.java new file mode 100644 index 00000000..ad188d21 --- /dev/null +++ b/src/test/java/com/checkmarx/ast/MaskTest.java @@ -0,0 +1,105 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.mask.MaskResult; +import com.checkmarx.ast.mask.MaskedSecret; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class MaskTest extends BaseTest { + + private static final String RESULTS_FILE = "target/test-classes/results.json"; + private static final String SECRETS_REALTIME_FILE = "target/test-classes/Secrets-realtime.json"; + + @Test + void testMaskSecretsWithFileContainingSecrets() throws Exception { + // Tests CLI execution with file containing actual secrets and validates masking behavior + MaskResult result = wrapper.maskSecrets(SECRETS_REALTIME_FILE); + + Assertions.assertNotNull(result); + Assertions.assertNotNull(result.getMaskedFile()); + Assertions.assertNotNull(result.getMaskedSecrets()); + Assertions.assertFalse(result.getMaskedSecrets().isEmpty()); + + MaskedSecret secret = result.getMaskedSecrets().get(0); + Assertions.assertNotNull(secret.getMasked()); + Assertions.assertNotNull(secret.getSecret()); + Assertions.assertEquals(5, secret.getLine()); + Assertions.assertTrue(secret.getMasked().contains("") || secret.getMasked().contains("\\u003cmasked\\u003e")); + Assertions.assertTrue(secret.getSecret().contains("-----BEGIN RSA PRIVATE KEY-----")); + Assertions.assertTrue(secret.getSecret().length() > secret.getMasked().length()); + } + + @Test + void testMaskSecretsWithFileContainingNoSecrets() throws Exception { + // Tests CLI execution with file containing no secrets + MaskResult result = wrapper.maskSecrets(RESULTS_FILE); + + Assertions.assertNotNull(result); + Assertions.assertNotNull(result.getMaskedFile()); + Assertions.assertFalse(result.getMaskedFile().isEmpty()); + } + + @Test + void testMaskSecretsErrorHandling() { + // Tests CLI error handling for invalid inputs + Assertions.assertThrows(Exception.class, () -> wrapper.maskSecrets(null)); + Assertions.assertThrows(Exception.class, () -> wrapper.maskSecrets("non-existent-file.json")); + Assertions.assertDoesNotThrow(() -> wrapper.maskSecrets(RESULTS_FILE)); + } + + @Test + void testMaskSecretsResponseParsing() throws Exception { + // Tests CLI response structure and JSON parsing functionality + MaskResult result = wrapper.maskSecrets(SECRETS_REALTIME_FILE); + + Assertions.assertNotNull(result); + Assertions.assertNotNull(result.getMaskedSecrets()); + Assertions.assertFalse(result.getMaskedSecrets().isEmpty()); + + MaskedSecret secret = result.getMaskedSecrets().get(0); + Assertions.assertNotNull(secret.getMasked()); + Assertions.assertNotNull(secret.getSecret()); + Assertions.assertTrue(secret.getLine() >= 0); + + Assertions.assertNull(MaskResult.fromLine("")); + Assertions.assertNull(MaskResult.fromLine("{invalid json}")); + Assertions.assertNull(MaskResult.fromLine(null)); + } + + @Test + void testMaskSecretsObjectBehavior() throws Exception { + // Tests object equality, serialization and consistency with CLI responses + MaskResult result1 = wrapper.maskSecrets(SECRETS_REALTIME_FILE); + MaskResult result2 = wrapper.maskSecrets(SECRETS_REALTIME_FILE); + + Assertions.assertEquals(result1.getMaskedFile(), result2.getMaskedFile()); + Assertions.assertNotNull(result1.toString()); + Assertions.assertTrue(result1.toString().contains("MaskResult")); + + if (result1.getMaskedSecrets() != null && !result1.getMaskedSecrets().isEmpty()) { + MaskedSecret secret1 = result1.getMaskedSecrets().get(0); + MaskedSecret secret2 = result2.getMaskedSecrets().get(0); + + Assertions.assertEquals(secret1.getMasked(), secret2.getMasked()); + Assertions.assertEquals(secret1.getSecret(), secret2.getSecret()); + Assertions.assertEquals(secret1.getLine(), secret2.getLine()); + Assertions.assertEquals(secret1.hashCode(), secret2.hashCode()); + Assertions.assertEquals(secret1, secret1); + Assertions.assertNotEquals(secret1, null); + + String toString = secret1.toString(); + Assertions.assertNotNull(toString); + Assertions.assertTrue(toString.contains("MaskedSecret")); + } + + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(result1); + MaskResult deserialized = mapper.readValue(json, MaskResult.class); + + Assertions.assertEquals(result1.getMaskedFile(), deserialized.getMaskedFile()); + if (result1.getMaskedSecrets() != null) { + Assertions.assertEquals(result1.getMaskedSecrets().size(), deserialized.getMaskedSecrets().size()); + } + } +} diff --git a/src/test/resources/Secrets-realtime.json b/src/test/resources/Secrets-realtime.json new file mode 100644 index 00000000..5acdd938 --- /dev/null +++ b/src/test/resources/Secrets-realtime.json @@ -0,0 +1,147 @@ +[ + { + "Title": "private-key", + "Description": "Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.", + "SecretValue": "-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAl5X22d9tXl2Bz1b+3mWsAouoBiQhrDS3GxFAdpJFkKF6Wst+Vl1mfshTd+gF2kHXTzLMdxsUM2AS8laG2xeIhLe07FhhOtQGSoAOjHD++K53MBcOD/mDVOlPhNOWAc3qWfa3R7ohxUJq8lvy1OErw7qlQv9U+xABUJtHbJtMn/dDBs1Fy6MUgO6TOtEwzQaTaGpWh3NmGaUu3KQuaekHZnzlYd8mc1XkztXrph0XxZPq43Gg8RVh25fCrcA+7iAkqa4MNL/5gsatzzjP7KRdx1IP0pnM6F/cwoMClmuV7FFhKoP3KFwt8SXglZnqLrCNK6DzrYU5bRaszIxYc8egywIDAQABAoIBADGaq2rkiF+m7cGx0Dlqv/0dQmCwFizKG1lKLfQfLZCEpwtrJ+6PJek7GMVWMgQYI6MRFoOrYtLlD44p7ntnmg8EJrpouXiMxXo/qYMfvvAV937PLJThq65vosvuiVoRziyeZZ+dM0vfzit9F1u+S5oDS+0+rMpzlFqSVa8eqtZ1i4wmVwKXF35FqHyzhXgKmVNXUy/JCiftJYF2hpe2zXAuGQKTD5x3v52GX+cUsRPPAO5GhJbDnwhbL8i06gLDc7xwfaTJbebvOKHT1F2AW/RhoW+BpJke86beea8DM7KR8RYjobgdf66fadosa27u8qqpcdtyzYMqLc3GuTeYL/ECgYEA36KgaxlWNCuQhLOLPZ/2fzebD1SCI7HsPtbiM+mSbWTGBnSPX36b1XLJkynZrAFKYoe9zx9ksqlrBHWG7PC3FLeIO9ExV9BsGFGjzDpkENteArcO5eLfSMkfXIXNlWwhb8m8DPjdnK2pihs3vVN0OMIPeYu9mAXst0CcR8vQuJ0CgYEArYYBgmAyWfRs8exU2F7vQoG8B4mykTOQ9J91Js72WiHaqt+z4NdGJrFr5VkXCZdbBf3J/PfEPVFT3ud/dAm2PZ40TfNQJTa4pwBhuyCozZff1Qm+X5NdzvHkLePFex6wxUgupVwr/W9UOVU7gRFB/hziSLnlglfpgwxA9j9tfocCgYEAqH69YzQp0RDpyDIGvR2i+WMJ/1jq3L4Xg5kfwYFAhA+jbAWyaH7aJs5ftfOYP5KRWv9vMXkzw7EGIsvyJt+O8Zr+mCMbjFBKwV/xi9SKxHCjumP2Y5q2JP70FB/0L5rS7okOmK+BOaVW0emD66/PJ1x/kFKLPNlp6wBRP37++bkCgYAgrkdkfaeeB4npOmB0a9TWCscWCFoIPNUFLW8MAxikuxGK8xzWsNS2ft3aUSAkn0v2YekD6sob3lBUf/ciLJ4VFtG1CKlEiPzX/xto+eqw5fSzE+W17HRTgH1AI1DTMmGKlmCqpiRm0+vh7GqLkWuDZ386wUA3f0UseEdX2XROywKBgE1Xer2yEZtLlrubHgVAKVrz2u5ZCEPzDkLEDhOxX2h1dP19TWvm2Sr6Fm1QL8lez52YhCW/xJHsr8S4Kka3Ntbm9+ZfhhATTICTpqIicqeAq/Iiw++7UgCk1gZPW1hnlDHYRdmI4Dr6j5aUBFLl4Bj2nedH+1L1Eo8EXfnjm0pi-----END RSA PRIVATE KEY-----", + "FilePath": "C:\\Users\\XYZ\\GitHub_Repo\\JavaVulnerableLab\\CxAppMonDeploy.pem", + "Severity": "High", + "Locations": [ + { + "Line": 0, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 1, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 2, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 3, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 4, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 5, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 6, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 7, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 8, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 9, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 10, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 11, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 12, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 13, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 14, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 15, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 16, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 17, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 18, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 19, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 20, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 21, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 22, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 23, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 24, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 25, + "StartIndex": 1, + "EndIndex": 30 + }, + { + "Line": 26, + "StartIndex": 1, + "EndIndex": 30 + } + ] + } +] + From dbd2b515793194db51348c391bc9ca4a00e296e8 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:20:13 +0530 Subject: [PATCH 23/37] Added fix for containerTool for IAC --- .../com/checkmarx/ast/wrapper/CxWrapper.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 85be850d..e25302bb 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -407,7 +407,7 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String return Execution.executeCommand(withConfigArguments(arguments), logger, KicsRealtimeResults::fromLine); } - public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String ignoredFilePath, java.util.function.Function resultParser) + public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String containerTool, String ignoredFilePath, java.util.function.Function resultParser) throws IOException, InterruptedException, CxException { this.logger.info("Executing 'scan {}' command using the CLI.", subCommand); this.logger.info("Source: {} IgnoredFilePath: {}", sourcePath, ignoredFilePath); @@ -416,6 +416,10 @@ public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath arguments.add(subCommand); arguments.add(CxConstants.SOURCE); arguments.add(sourcePath); + if(StringUtils.isNotBlank(containerTool)){ + arguments.add(CxConstants.ENGINE); + arguments.add(containerTool); + } if (StringUtils.isNotBlank(ignoredFilePath)) { arguments.add(CxConstants.IGNORED_FILE_PATH); arguments.add(ignoredFilePath); @@ -426,25 +430,26 @@ public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath // OSS Realtime public OssRealtimeResults ossRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_OSS_REALTIME, sourcePath, ignoredFilePath, OssRealtimeResults::fromLine); + return realtimeScan(CxConstants.SUB_CMD_OSS_REALTIME, sourcePath,"", ignoredFilePath, OssRealtimeResults::fromLine); } // IAC Realtime - public IacRealtimeResults iacRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) + public IacRealtimeResults iacRealtimeScan(@NonNull String sourcePath,String containerTool, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath, ignoredFilePath, IacRealtimeResults::fromLine); + return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath,containerTool, ignoredFilePath, IacRealtimeResults::fromLine); } + // Secrets Realtime public SecretsRealtimeResults secretsRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath, ignoredFilePath, SecretsRealtimeResults::fromLine); + return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath,"", ignoredFilePath, SecretsRealtimeResults::fromLine); } // Containers Realtime public ContainersRealtimeResults containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath) throws IOException, InterruptedException, CxException { - return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, ignoredFilePath, ContainersRealtimeResults::fromLine); + return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, "",ignoredFilePath, ContainersRealtimeResults::fromLine); } public KicsRemediation kicsRemediate(@NonNull String resultsFile, String kicsFile, String engine,String similarityIds) From 13c6f10cfbb27bf56a8a236672129d6032ba7fdd Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:39:45 +0530 Subject: [PATCH 24/37] Add telemetry AI command with full parameter support and tests --- .../checkmarx/ast/wrapper/CxConstants.java | 9 +++ .../com/checkmarx/ast/wrapper/CxWrapper.java | 48 +++++++++++++ .../java/com/checkmarx/ast/TelemetryTest.java | 67 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 src/test/java/com/checkmarx/ast/TelemetryTest.java diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index dd8c6e69..79f171c2 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -82,4 +82,13 @@ public final class CxConstants { static final String SUB_CMD_CONTAINERS_REALTIME = "containers-realtime"; static final String SUB_CMD_MASK = "mask"; static final String RESULT_FILE = "--result-file"; + static final String CMD_TELEMETRY = "telemetry"; + static final String SUB_CMD_TELEMETRY_AI = "ai"; + static final String AI_PROVIDER = "--ai-provider"; + static final String TYPE = "--type"; + static final String SUB_TYPE = "--sub-type"; + static final String PROBLEM_SEVERITY = "--problem-severity"; + static final String SCAN_TYPE_FLAG = "--scan-type"; + static final String STATUS = "--status"; + static final String TOTAL_COUNT = "--total-count"; } diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 9603b26e..6d027c7c 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -537,6 +537,54 @@ public MaskResult maskSecrets(@NonNull String filePath) throws CxException, IOEx return Execution.executeCommand(withConfigArguments(arguments), logger, MaskResult::fromLine); } + /** + * Executes telemetry AI command to collect telemetry data for user interactions related to AI features. + * + * @param aiProvider AI provider name (e.g., "Cursor") + * @param agent Agent name + * @param eventType Event type (e.g., "click") + * @param subType Event subtype (e.g., "ast-results.viewPackageDetails") + * @param engine Engine type (e.g., "secrets") + * @param problemSeverity Severity level (e.g., "high") + * @param scanType Type of scan + * @param status Status information + * @param totalCount Number count + * @return Command output as string + * @throws IOException if I/O error occurs + * @throws InterruptedException if command execution is interrupted + * @throws CxException if CLI command fails + */ + public String telemetryAIEvent(String aiProvider, String agent, String eventType, String subType, + String engine, String problemSeverity, String scanType, String status, + Integer totalCount) throws IOException, InterruptedException, CxException { + this.logger.info("Executing telemetry AI event with provider: {}, type: {}, subType: {}", + aiProvider, eventType, subType); + + List arguments = new ArrayList<>(); + arguments.add(CxConstants.CMD_TELEMETRY); + arguments.add(CxConstants.SUB_CMD_TELEMETRY_AI); + arguments.add(CxConstants.AI_PROVIDER); + arguments.add(aiProvider); + arguments.add(CxConstants.AGENT); + arguments.add(agent); + arguments.add(CxConstants.TYPE); + arguments.add(eventType); + arguments.add(CxConstants.SUB_TYPE); + arguments.add(subType); + arguments.add(CxConstants.ENGINE); + arguments.add(engine); + arguments.add(CxConstants.PROBLEM_SEVERITY); + arguments.add(problemSeverity); + arguments.add(CxConstants.SCAN_TYPE_FLAG); + arguments.add(scanType); + arguments.add(CxConstants.STATUS); + arguments.add(status); + arguments.add(CxConstants.TOTAL_COUNT); + arguments.add(totalCount.toString()); + + return Execution.executeCommand(withConfigArguments(arguments), logger, line -> line); + } + private int getIndexOfBfLNode(List bflNodes, List resultNodes) { int bflNodeNotFound = -1; diff --git a/src/test/java/com/checkmarx/ast/TelemetryTest.java b/src/test/java/com/checkmarx/ast/TelemetryTest.java new file mode 100644 index 00000000..a014aa65 --- /dev/null +++ b/src/test/java/com/checkmarx/ast/TelemetryTest.java @@ -0,0 +1,67 @@ +package com.checkmarx.ast; + +import com.checkmarx.ast.wrapper.CxException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +/** + * Telemetry AI event test cases covering various parameter scenarios. + */ +class TelemetryTest extends BaseTest { + + @Test + void testTelemetryAIEventSuccessfulCaseWithMinimalParametersAiLog() throws CxException, IOException, InterruptedException { + // Test case: AI logging with specific parameters and some empty values + Assertions.assertDoesNotThrow(() -> { + String result = wrapper.telemetryAIEvent( + "Cursor", // aiProvider + "Cursos", // agent + "click", // eventType + "ast-results.viewPackageDetails", // subType + "secrets", // engine + "high", // problemSeverity + "", // scanType (empty) + "", // status (empty) + 0 // totalCount + ); + }, "Telemetry AI event should execute successfully"); + } + + @Test + void testTelemetryAIEventSuccessfulCaseWithMinimalParametersDetectionLog() throws CxException, IOException, InterruptedException { + // Test case: Detection logging with most parameters empty and specific scan data + Assertions.assertDoesNotThrow(() -> { + String result = wrapper.telemetryAIEvent( + "", // aiProvider (empty) + "", // agent (empty) + "", // eventType (empty) + "", // subType (empty) + "", // engine (empty) + "", // problemSeverity (empty) + "asca", // scanType + "Critical", // status + 10 // totalCount + ); + }, "Telemetry AI event should execute successfully for detection log"); + } + + @Test + void testTelemetryAIEventSuccessfulCaseWithEdgeCaseParameters() throws CxException, IOException, InterruptedException { + // Test case: Edge case with minimal required parameters + Assertions.assertDoesNotThrow(() -> { + String result = wrapper.telemetryAIEvent( + "test-provider", // aiProvider (minimal value) + "java-wrapper", // agent (minimal value) + "", // eventType (empty) + "", // subType (empty) + "", // engine (empty) + "", // problemSeverity (empty) + "", // scanType (empty) + "", // status (empty) + 0 // totalCount + ); + }, "Telemetry AI event should execute successfully for edge case"); + } +} From 36872e6af2c4c38bcefaa018dd26597faf43f1fc Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 24 Dec 2025 11:43:09 +0530 Subject: [PATCH 25/37] Add ignoredFilePath parameter to ScanAsca method --- .../com/checkmarx/ast/wrapper/CxWrapper.java | 10 +++++++--- src/test/java/com/checkmarx/ast/ScanTest.java | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 6d027c7c..bdcd4482 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -248,7 +248,7 @@ public List projectList(String filter) throws IOException, InterruptedE return Execution.executeCommand(withConfigArguments(arguments), logger, Project::listFromLine); } - public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String agent) throws IOException, InterruptedException, CxException { + public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String agent, String ignoredFilePath) throws IOException, InterruptedException, CxException { this.logger.info("Fetching ASCA scanResult"); List arguments = new ArrayList<>(); @@ -259,6 +259,10 @@ public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String if (ascaLatestVersion) { arguments.add(CxConstants.ASCA_LATEST_VERSION); } + if (StringUtils.isNotBlank(ignoredFilePath)) { + arguments.add(CxConstants.IGNORED_FILE_PATH); + arguments.add(ignoredFilePath); + } appendAgentToArguments(agent, arguments); @@ -540,8 +544,8 @@ public MaskResult maskSecrets(@NonNull String filePath) throws CxException, IOEx /** * Executes telemetry AI command to collect telemetry data for user interactions related to AI features. * - * @param aiProvider AI provider name (e.g., "Cursor") - * @param agent Agent name + * @param aiProvider AI provider name (e.g., "Copilot") + * @param agent Agent name (e.g., "Jetbrains") * @param eventType Event type (e.g., "click") * @param subType Event subtype (e.g., "ast-results.viewPackageDetails") * @param engine Engine type (e.g., "secrets") diff --git a/src/test/java/com/checkmarx/ast/ScanTest.java b/src/test/java/com/checkmarx/ast/ScanTest.java index 5e31b337..a414281e 100644 --- a/src/test/java/com/checkmarx/ast/ScanTest.java +++ b/src/test/java/com/checkmarx/ast/ScanTest.java @@ -25,7 +25,7 @@ void testScanShow() throws Exception { @Test void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulResponseWithCorrectValues() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode"); + ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode", null); // Assertions for the scan result Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null"); @@ -46,7 +46,7 @@ void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulRes @Test void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseWithCorrectValues() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null); + ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null, null); Assertions.assertNotNull(scanResult.getRequestId()); Assertions.assertTrue(scanResult.isStatus()); Assertions.assertNull(scanResult.getError()); @@ -55,12 +55,25 @@ void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseW @Test void testScanAsca_WhenMissingFileExtension_ReturnFileExtensionIsRequiredFailure() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null); + ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null, null); Assertions.assertNotNull(scanResult.getRequestId()); Assertions.assertNotNull(scanResult.getError()); Assertions.assertEquals("The file name must have an extension.", scanResult.getError().getDescription()); } + @Test + void testScanAsca_WithIgnoreFilePath_ShouldWorkCorrectly() throws Exception { + String ignoreFile = "src/test/resources/ignored-packages.json"; + + // Test with ignore file - should not break the scanning process + ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "test-agent", ignoreFile); + + // Verify the scan completes successfully + Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null"); + Assertions.assertTrue(scanResult.isStatus(), "Status should be true"); + Assertions.assertNull(scanResult.getError(), "Error should be null when scan is successful"); + } + @Test void testScanList() throws Exception { List cxOutput = wrapper.scanList("limit=10"); From 65d7e2aa4cbbb12f08567fbb8c890324e79dac91 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Mon, 29 Dec 2025 13:31:52 +0530 Subject: [PATCH 26/37] Removing ASCA ignore file path changes --- .../com/checkmarx/ast/wrapper/CxWrapper.java | 6 +----- src/test/java/com/checkmarx/ast/ScanTest.java | 19 +++---------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index bdcd4482..386133ad 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -248,7 +248,7 @@ public List projectList(String filter) throws IOException, InterruptedE return Execution.executeCommand(withConfigArguments(arguments), logger, Project::listFromLine); } - public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String agent, String ignoredFilePath) throws IOException, InterruptedException, CxException { + public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String agent) throws IOException, InterruptedException, CxException { this.logger.info("Fetching ASCA scanResult"); List arguments = new ArrayList<>(); @@ -259,10 +259,6 @@ public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String if (ascaLatestVersion) { arguments.add(CxConstants.ASCA_LATEST_VERSION); } - if (StringUtils.isNotBlank(ignoredFilePath)) { - arguments.add(CxConstants.IGNORED_FILE_PATH); - arguments.add(ignoredFilePath); - } appendAgentToArguments(agent, arguments); diff --git a/src/test/java/com/checkmarx/ast/ScanTest.java b/src/test/java/com/checkmarx/ast/ScanTest.java index a414281e..5e31b337 100644 --- a/src/test/java/com/checkmarx/ast/ScanTest.java +++ b/src/test/java/com/checkmarx/ast/ScanTest.java @@ -25,7 +25,7 @@ void testScanShow() throws Exception { @Test void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulResponseWithCorrectValues() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode", null); + ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode"); // Assertions for the scan result Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null"); @@ -46,7 +46,7 @@ void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulRes @Test void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseWithCorrectValues() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null, null); + ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null); Assertions.assertNotNull(scanResult.getRequestId()); Assertions.assertTrue(scanResult.isStatus()); Assertions.assertNull(scanResult.getError()); @@ -55,25 +55,12 @@ void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseW @Test void testScanAsca_WhenMissingFileExtension_ReturnFileExtensionIsRequiredFailure() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null, null); + ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null); Assertions.assertNotNull(scanResult.getRequestId()); Assertions.assertNotNull(scanResult.getError()); Assertions.assertEquals("The file name must have an extension.", scanResult.getError().getDescription()); } - @Test - void testScanAsca_WithIgnoreFilePath_ShouldWorkCorrectly() throws Exception { - String ignoreFile = "src/test/resources/ignored-packages.json"; - - // Test with ignore file - should not break the scanning process - ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "test-agent", ignoreFile); - - // Verify the scan completes successfully - Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null"); - Assertions.assertTrue(scanResult.isStatus(), "Status should be true"); - Assertions.assertNull(scanResult.getError(), "Error should be null when scan is successful"); - } - @Test void testScanList() throws Exception { List cxOutput = wrapper.scanList("limit=10"); From 1adc1f433fd2800082360ebf4d741f8bcfc50b34 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:31:56 +0530 Subject: [PATCH 27/37] Add ignoredFilePath parameter to ScanAsca realtime --- .../com/checkmarx/ast/wrapper/CxWrapper.java | 6 +++++- src/test/java/com/checkmarx/ast/ScanTest.java | 19 ++++++++++++++++--- .../java/com/checkmarx/ast/TelemetryTest.java | 12 ++++++------ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 386133ad..bdcd4482 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -248,7 +248,7 @@ public List projectList(String filter) throws IOException, InterruptedE return Execution.executeCommand(withConfigArguments(arguments), logger, Project::listFromLine); } - public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String agent) throws IOException, InterruptedException, CxException { + public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String agent, String ignoredFilePath) throws IOException, InterruptedException, CxException { this.logger.info("Fetching ASCA scanResult"); List arguments = new ArrayList<>(); @@ -259,6 +259,10 @@ public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String if (ascaLatestVersion) { arguments.add(CxConstants.ASCA_LATEST_VERSION); } + if (StringUtils.isNotBlank(ignoredFilePath)) { + arguments.add(CxConstants.IGNORED_FILE_PATH); + arguments.add(ignoredFilePath); + } appendAgentToArguments(agent, arguments); diff --git a/src/test/java/com/checkmarx/ast/ScanTest.java b/src/test/java/com/checkmarx/ast/ScanTest.java index 5e31b337..a414281e 100644 --- a/src/test/java/com/checkmarx/ast/ScanTest.java +++ b/src/test/java/com/checkmarx/ast/ScanTest.java @@ -25,7 +25,7 @@ void testScanShow() throws Exception { @Test void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulResponseWithCorrectValues() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode"); + ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode", null); // Assertions for the scan result Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null"); @@ -46,7 +46,7 @@ void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulRes @Test void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseWithCorrectValues() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null); + ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null, null); Assertions.assertNotNull(scanResult.getRequestId()); Assertions.assertTrue(scanResult.isStatus()); Assertions.assertNull(scanResult.getError()); @@ -55,12 +55,25 @@ void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseW @Test void testScanAsca_WhenMissingFileExtension_ReturnFileExtensionIsRequiredFailure() throws Exception { - ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null); + ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null, null); Assertions.assertNotNull(scanResult.getRequestId()); Assertions.assertNotNull(scanResult.getError()); Assertions.assertEquals("The file name must have an extension.", scanResult.getError().getDescription()); } + @Test + void testScanAsca_WithIgnoreFilePath_ShouldWorkCorrectly() throws Exception { + String ignoreFile = "src/test/resources/ignored-packages.json"; + + // Test with ignore file - should not break the scanning process + ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "test-agent", ignoreFile); + + // Verify the scan completes successfully + Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null"); + Assertions.assertTrue(scanResult.isStatus(), "Status should be true"); + Assertions.assertNull(scanResult.getError(), "Error should be null when scan is successful"); + } + @Test void testScanList() throws Exception { List cxOutput = wrapper.scanList("limit=10"); diff --git a/src/test/java/com/checkmarx/ast/TelemetryTest.java b/src/test/java/com/checkmarx/ast/TelemetryTest.java index a014aa65..7f7ee4bb 100644 --- a/src/test/java/com/checkmarx/ast/TelemetryTest.java +++ b/src/test/java/com/checkmarx/ast/TelemetryTest.java @@ -16,10 +16,10 @@ void testTelemetryAIEventSuccessfulCaseWithMinimalParametersAiLog() throws CxExc // Test case: AI logging with specific parameters and some empty values Assertions.assertDoesNotThrow(() -> { String result = wrapper.telemetryAIEvent( - "Cursor", // aiProvider - "Cursos", // agent + "Copilot", // aiProvider + "JetBrains IntelliJ IDEA", // agent "click", // eventType - "ast-results.viewPackageDetails", // subType + "viewDetails", // subType "secrets", // engine "high", // problemSeverity "", // scanType (empty) @@ -34,7 +34,7 @@ void testTelemetryAIEventSuccessfulCaseWithMinimalParametersDetectionLog() throw // Test case: Detection logging with most parameters empty and specific scan data Assertions.assertDoesNotThrow(() -> { String result = wrapper.telemetryAIEvent( - "", // aiProvider (empty) + "", // aiProvider (empty) "", // agent (empty) "", // eventType (empty) "", // subType (empty) @@ -52,7 +52,7 @@ void testTelemetryAIEventSuccessfulCaseWithEdgeCaseParameters() throws CxExcepti // Test case: Edge case with minimal required parameters Assertions.assertDoesNotThrow(() -> { String result = wrapper.telemetryAIEvent( - "test-provider", // aiProvider (minimal value) + "test-provider", // aiProvider (minimal value) "java-wrapper", // agent (minimal value) "", // eventType (empty) "", // subType (empty) @@ -64,4 +64,4 @@ void testTelemetryAIEventSuccessfulCaseWithEdgeCaseParameters() throws CxExcepti ); }, "Telemetry AI event should execute successfully for edge case"); } -} +} \ No newline at end of file From 6fdd4cd22bd7fbab45be782492fac45949536a57 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:08:47 +0530 Subject: [PATCH 28/37] - Changed ast cli version --- checkmarx-ast-cli.version | 2 +- src/main/resources/cx.exe | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 src/main/resources/cx.exe diff --git a/checkmarx-ast-cli.version b/checkmarx-ast-cli.version index 81309a31..27920931 100644 --- a/checkmarx-ast-cli.version +++ b/checkmarx-ast-cli.version @@ -1 +1 @@ -2.3.41 +2.3.42-iac-engine-fallbackPath \ No newline at end of file diff --git a/src/main/resources/cx.exe b/src/main/resources/cx.exe deleted file mode 100644 index 95849445..00000000 --- a/src/main/resources/cx.exe +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf0e0f8c3a721f9e2e21671e9a1954d9ac132a6621c076e467e1bfd51b9a2269 -size 82975168 From fc729649debd37e65c7c9842dc52c9d0effb179a Mon Sep 17 00:00:00 2001 From: Hitesh Madgulkar <212497904+cx-hitesh-madgulkar@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:25:26 +0530 Subject: [PATCH 29/37] added-isdev-isoneassist-function --- .../checkmarx/ast/wrapper/CxConstants.java | 2 ++ .../com/checkmarx/ast/wrapper/CxWrapper.java | 22 +++++++++++++++++++ .../java/com/checkmarx/ast/TenantTest.java | 10 +++++++++ 3 files changed, 34 insertions(+) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java index 79f171c2..450475de 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java @@ -75,6 +75,8 @@ public final class CxConstants { static final String SUB_CMD_TENANT = "tenant"; static final String IDE_SCANS_KEY = "scan.config.plugins.ideScans"; static final String AI_MCP_SERVER_KEY = "scan.config.plugins.aiMcpServer"; + static final String DEV_ASSIST_LICENSE_KEY = "scan.config.plugins.cxdevassist"; + static final String ONE_ASSIST_LICENSE_KEY = "scan.config.plugins.cxoneassist"; static final String IGNORED_FILE_PATH = "--ignored-file-path"; static final String SUB_CMD_OSS_REALTIME = "oss-realtime"; static final String SUB_CMD_IAC_REALTIME = "iac-realtime"; diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index bdcd4482..557db8bb 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -530,6 +530,28 @@ public List tenantSettings() throws CxException, IOException, Int return Execution.executeCommand(withConfigArguments(arguments), logger, TenantSetting::listFromLine); } + + + public boolean getTenantSetting(String key) throws CxException, IOException, InterruptedException { + List tenantSettings = tenantSettings(); + if (tenantSettings == null) { + throw new CxException(1, "Unable to parse tenant settings"); + } + return tenantSettings.stream() + .filter(t -> t.getKey().equals(key)) + .findFirst() + .map(t -> Boolean.parseBoolean(t.getValue())) + .orElse(false); + } + public boolean devAssistEnabled() throws CxException, IOException, InterruptedException { + return getTenantSetting(CxConstants.DEV_ASSIST_LICENSE_KEY); + + } + + public boolean oneAssistEnabled() throws CxException, IOException, InterruptedException { + return getTenantSetting(CxConstants.ONE_ASSIST_LICENSE_KEY); + } + public MaskResult maskSecrets(@NonNull String filePath) throws CxException, IOException, InterruptedException { List arguments = new ArrayList<>(); diff --git a/src/test/java/com/checkmarx/ast/TenantTest.java b/src/test/java/com/checkmarx/ast/TenantTest.java index 7f49da16..91824f4e 100644 --- a/src/test/java/com/checkmarx/ast/TenantTest.java +++ b/src/test/java/com/checkmarx/ast/TenantTest.java @@ -24,4 +24,14 @@ void testAiMcpServerEnabled() throws Exception { boolean enabled = Assertions.assertDoesNotThrow(() -> wrapper.aiMcpServerEnabled()); Assertions.assertTrue(enabled, "AI MCP Server flag expected to be true"); } + + @Test + void testDevAssistEnabled() { + Assertions.assertDoesNotThrow(() -> wrapper.devAssistEnabled()); + } + + @Test + void testOneAssistEnabled() { + Assertions.assertDoesNotThrow(() -> wrapper.oneAssistEnabled()); + } } From 0bd805ccff6b53ec64b60a316d13c2f5fe851792 Mon Sep 17 00:00:00 2001 From: Atish Jadhav <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:05:01 +0530 Subject: [PATCH 30/37] - Adding engine check --- .../com/checkmarx/ast/wrapper/CxWrapper.java | 34 ++++++++++++++++--- .../com/checkmarx/ast/wrapper/Execution.java | 10 +++--- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 386133ad..d10afd39 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -31,15 +31,17 @@ import java.io.IOException; import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; + +import static com.checkmarx.ast.wrapper.Execution.*; public class CxWrapper { private static final CollectionType BRANCHES_TYPE = TypeFactory.defaultInstance() .constructCollectionType(List.class, String.class); + private static final String OS_LINUX = "linux"; + private static final String OS_WINDOWS = "windows"; + private static final String OS_MAC = "mac"; @NonNull private final CxConfig cxConfig; @@ -409,6 +411,30 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String return Execution.executeCommand(withConfigArguments(arguments), logger, KicsRealtimeResults::fromLine); } + public String checkEngineExist(@NonNull String engineName) throws CxException, IOException, InterruptedException { + String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + String osType=Execution.getOperatingSystemType(osName); + return this.checkEngine(engineName,osType); + } + + private String checkEngine(String engineName, String osType ) throws CxException, IOException, InterruptedException { + List arguments = new ArrayList<>(); + switch (osType){ + case OS_LINUX: + case OS_MAC: + arguments.add("/bin/sh"); + arguments.add("-c"); + arguments.add("which " + engineName); + break; + case OS_WINDOWS: + arguments.add(engineName); + arguments.add("--version"); + break; + + } + return Execution.executeCommand((arguments), logger, line->line); + } + public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String containerTool, String ignoredFilePath, java.util.function.Function resultParser) throws IOException, InterruptedException, CxException { this.logger.info("Executing 'scan {}' command using the CLI.", subCommand); diff --git a/src/main/java/com/checkmarx/ast/wrapper/Execution.java b/src/main/java/com/checkmarx/ast/wrapper/Execution.java index c233ff2d..0a888ec0 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/Execution.java +++ b/src/main/java/com/checkmarx/ast/wrapper/Execution.java @@ -1,5 +1,6 @@ package com.checkmarx.ast.wrapper; +import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults; import lombok.NonNull; import org.slf4j.Logger; @@ -12,10 +13,7 @@ import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.Objects; +import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; @@ -171,7 +169,7 @@ private static String detectBinaryName(@NonNull Logger logger) { return fileName; } - private static String getOperatingSystemType(String osName) { + public static String getOperatingSystemType(String osName) { if (osName.contains(OS_LINUX)) { return OS_LINUX; } else if (osName.contains(OS_WINDOWS)) { @@ -217,4 +215,6 @@ private static String md5(InputStream a) { } return md5; } + + } From 7609d9f74519f52193d0ab324318dbb0b5fe18a0 Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:49:07 +0530 Subject: [PATCH 31/37] - Added check for engine verification in path --- .../com/checkmarx/ast/wrapper/CxWrapper.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index d10afd39..138e7695 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -420,19 +420,40 @@ public String checkEngineExist(@NonNull String engineName) throws CxException, I private String checkEngine(String engineName, String osType ) throws CxException, IOException, InterruptedException { List arguments = new ArrayList<>(); switch (osType){ - case OS_LINUX: case OS_MAC: + String enginePath; arguments.add("/bin/sh"); arguments.add("-c"); - arguments.add("which " + engineName); - break; + arguments.add("command -v " + engineName); + try{ + enginePath= Execution.executeCommand((arguments), logger, line->line); + } + catch (CxException e){ + throw new CxException(1,"Engine "+engineName+" is not installed on the system"); + } + + if(!enginePath.startsWith("/usr/local/bin/")){ + throw new CxException(1, engineName+ " was found at: " + enginePath + "\n" + + "Please create a symlink at /usr/local/bin/docker:\n\n" + + "sudo ln -s " + enginePath + " /usr/local/bin/"+engineName +"\n"); + } + return enginePath; case OS_WINDOWS: + case OS_LINUX: arguments.add(engineName); arguments.add("--version"); - break; - + try { + Execution.executeCommand(arguments, logger, line -> line); + return engineName; // docker is available via PATH + } catch (CxException | IOException e) { + throw new CxException( + 1,engineName+" is not installed or is not accessible from the system PATH." + ); + } + default: + throw new IllegalArgumentException("Unsupported OS: " + osType); } - return Execution.executeCommand((arguments), logger, line->line); + } public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String containerTool, String ignoredFilePath, java.util.function.Function resultParser) From c556e247fac29818f04c16b3a59b53ce07d9750c Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:41:27 +0530 Subject: [PATCH 32/37] - Added checks message for exception --- src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 138e7695..fc337115 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -429,14 +429,9 @@ private String checkEngine(String engineName, String osType ) throws CxExceptio enginePath= Execution.executeCommand((arguments), logger, line->line); } catch (CxException e){ - throw new CxException(1,"Engine "+engineName+" is not installed on the system"); + throw new CxException(1,"Engine "+engineName+" is not installed on the system or not set at location /usr/local/bin"); } - if(!enginePath.startsWith("/usr/local/bin/")){ - throw new CxException(1, engineName+ " was found at: " + enginePath + "\n" + - "Please create a symlink at /usr/local/bin/docker:\n\n" + - "sudo ln -s " + enginePath + " /usr/local/bin/"+engineName +"\n"); - } return enginePath; case OS_WINDOWS: case OS_LINUX: @@ -444,7 +439,7 @@ private String checkEngine(String engineName, String osType ) throws CxExceptio arguments.add("--version"); try { Execution.executeCommand(arguments, logger, line -> line); - return engineName; // docker is available via PATH + return engineName; } catch (CxException | IOException e) { throw new CxException( 1,engineName+" is not installed or is not accessible from the system PATH." From 1c24aa955891d0c814fb63498c182e7d5323890e Mon Sep 17 00:00:00 2001 From: anjali-deore <200181980+cx-anjali-deore@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:05:33 +0530 Subject: [PATCH 33/37] - Added checks message for exception --- src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index fc337115..ad2fec89 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -428,9 +428,11 @@ private String checkEngine(String engineName, String osType ) throws CxExceptio try{ enginePath= Execution.executeCommand((arguments), logger, line->line); } - catch (CxException e){ - throw new CxException(1,"Engine "+engineName+" is not installed on the system or not set at location /usr/local/bin"); - } + throw new CxException( + 1, + "Engine '" + engineName + "' is not installed or not found at /usr/local/bin)." + ); + return enginePath; case OS_WINDOWS: From eb5d64699e59599fcb8ed56e935e6b6c2c26f90c Mon Sep 17 00:00:00 2001 From: Hitesh Madgulkar <212497904+cx-hitesh-madgulkar@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:12:48 +0530 Subject: [PATCH 34/37] passing-agent-name-jb-in-all-cmd --- .vscode/.checkmarxIgnored | 1 + .../com/checkmarx/ast/wrapper/CxConfig.java | 6 ++++- .../com/checkmarx/ast/wrapper/CxWrapper.java | 26 +++++++------------ 3 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 .vscode/.checkmarxIgnored diff --git a/.vscode/.checkmarxIgnored b/.vscode/.checkmarxIgnored new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/.vscode/.checkmarxIgnored @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java b/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java index f23a5ea8..ab0a7329 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java @@ -16,7 +16,7 @@ public class CxConfig { private static final Pattern pattern = Pattern.compile("([^\"]\\S*|\".+?\")\\s*"); - + private String agentName; //JETBRAINS private String baseUri; private String baseAuthUri; private String tenant; @@ -66,6 +66,10 @@ List toArguments() { commands.add(CxConstants.BASE_AUTH_URI); commands.add(getBaseAuthUri()); } + if (getAgentName() != null && !getAgentName().isEmpty()) { + commands.add("--agent"); + commands.add(getAgentName()); + } if (getAdditionalParameters() != null) commands.addAll(getAdditionalParameters()); diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 557db8bb..b7c28fd8 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -264,22 +264,22 @@ public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String arguments.add(ignoredFilePath); } - appendAgentToArguments(agent, arguments); + return Execution.executeCommand(withConfigArguments(arguments), logger, ScanResult::fromLine, (args, ignored) -> (args.size() >= 3 && args.get(1).equals(CxConstants.CMD_SCAN) && args.get(2).equals(CxConstants.SUB_CMD_ASCA))); } - private static void appendAgentToArguments(String agent, List arguments) { - arguments.add(CxConstants.AGENT); - if (agent != null && !agent.isEmpty()){ - arguments.add(agent); - } - else{ - arguments.add("CLI-Java-Wrapper"); - } - } + // private static void appendAgentToArguments(String agent, List arguments) { + // arguments.add(CxConstants.AGENT); + // if (agent != null && !agent.isEmpty()){ + // arguments.add(agent); + // } + // else{ + // arguments.add("CLI-Java-Wrapper"); + // } + // } public List projectBranches(@NonNull UUID projectId, String filter) throws CxException, IOException, InterruptedException { @@ -349,10 +349,6 @@ public String results(@NonNull UUID scanId, ReportFormat reportFormat, String ag arguments.add(fileName); arguments.add(CxConstants.OUTPUT_PATH); arguments.add(tempDir); - if (agent != null) { - arguments.add(CxConstants.AGENT); - arguments.add(agent); - } return Execution.executeCommand(arguments, logger, tempDir, fileName + reportFormat.getExtension()); @@ -591,8 +587,6 @@ public String telemetryAIEvent(String aiProvider, String agent, String eventType arguments.add(CxConstants.SUB_CMD_TELEMETRY_AI); arguments.add(CxConstants.AI_PROVIDER); arguments.add(aiProvider); - arguments.add(CxConstants.AGENT); - arguments.add(agent); arguments.add(CxConstants.TYPE); arguments.add(eventType); arguments.add(CxConstants.SUB_TYPE); From b2eeb56e5c5e444db5a4fd4037b7816639e5b97b Mon Sep 17 00:00:00 2001 From: Hitesh Madgulkar <212497904+cx-hitesh-madgulkar@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:53:21 +0530 Subject: [PATCH 35/37] merge-fix --- src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java index 145847a7..4060c4c8 100644 --- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java +++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java @@ -411,7 +411,7 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String return Execution.executeCommand(withConfigArguments(arguments), logger, KicsRealtimeResults::fromLine); } - public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String containerTool, String ignoredFilePath, java.util.function.Function resultParser) + // public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String containerTool, String ignoredFilePath, java.util.function.Function resultParser) public String checkEngineExist(@NonNull String engineName) throws CxException, IOException, InterruptedException { String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); String osType=Execution.getOperatingSystemType(osName); @@ -428,13 +428,12 @@ private String checkEngine(String engineName, String osType ) throws CxExceptio arguments.add("command -v " + engineName); try{ enginePath= Execution.executeCommand((arguments), logger, line->line); - } + } catch (CxException | IOException e) { throw new CxException( 1, "Engine '" + engineName + "' is not installed or not found at /usr/local/bin)." ); - - + } return enginePath; case OS_WINDOWS: case OS_LINUX: From 8b47577209497e9e8edb4af514d9ae12255e9438 Mon Sep 17 00:00:00 2001 From: Hitesh Madgulkar <212497904+cx-hitesh-madgulkar@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:41:42 +0530 Subject: [PATCH 36/37] placed-cli-exe --- .vscode/.checkmarxIgnored | 1 - src/main/resources/cx.exe | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 .vscode/.checkmarxIgnored create mode 100644 src/main/resources/cx.exe diff --git a/.vscode/.checkmarxIgnored b/.vscode/.checkmarxIgnored deleted file mode 100644 index 9e26dfee..00000000 --- a/.vscode/.checkmarxIgnored +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/main/resources/cx.exe b/src/main/resources/cx.exe new file mode 100644 index 00000000..0341df07 --- /dev/null +++ b/src/main/resources/cx.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fb41bc7c8d5b3db18f4626d9523fc626fd19c6f88e1bb078184e7b6e2532fed +size 82995136 From 9403703f0e657740dca5cd012249141af320f336 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:39:13 +0530 Subject: [PATCH 37/37] Revert "Merge branch 'main' into feature/ASCA_IgnoreFile" This reverts commit 7a6d22942a68521452cc27428cd1711ebead0cd3, reversing changes made to 8b47577209497e9e8edb4af514d9ae12255e9438. --- checkmarx-ast-cli.version | 3 +-- src/main/resources/cx-linux | 4 ++-- src/main/resources/cx-linux-arm | 2 +- src/main/resources/cx-mac | 4 ++-- src/main/resources/cx.exe | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/checkmarx-ast-cli.version b/checkmarx-ast-cli.version index 6592ef2e..27920931 100644 --- a/checkmarx-ast-cli.version +++ b/checkmarx-ast-cli.version @@ -1,2 +1 @@ -2.3.43 - +2.3.42-iac-engine-fallbackPath \ No newline at end of file diff --git a/src/main/resources/cx-linux b/src/main/resources/cx-linux index 5bd22e20..f9909fd8 100755 --- a/src/main/resources/cx-linux +++ b/src/main/resources/cx-linux @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2ec1fcc76f04596c976b5039ff8d755f3e6534f06ecf442cb8d22c4f3e8f9c8 -size 81055928 +oid sha256:2972512d55630f04f494fdf3543a3edf37906b970f5e29f58127ad27c40b652c +size 81023160 diff --git a/src/main/resources/cx-linux-arm b/src/main/resources/cx-linux-arm index fe820c8d..f17f02d5 100755 --- a/src/main/resources/cx-linux-arm +++ b/src/main/resources/cx-linux-arm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23dbe8b789c4c358023a98c15fdc57a468fff934804bbbfbb3ec5fa90d803c11 +oid sha256:ba3e45134be18e2093521df1f5e337c749e231fc926d5928815be4bbc68c4dd0 size 77332664 diff --git a/src/main/resources/cx-mac b/src/main/resources/cx-mac index 5423173f..52f3f40f 100755 --- a/src/main/resources/cx-mac +++ b/src/main/resources/cx-mac @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0a89e14ad8516e093625d3eff5713c83696d12494664c338839731f8dd8a79e -size 163042800 +oid sha256:7da05ed86e02142c414ebfa2ed335b71357f7d407291b9952bf5860c7bf78dab +size 162975392 diff --git a/src/main/resources/cx.exe b/src/main/resources/cx.exe index a71d0477..0341df07 100644 --- a/src/main/resources/cx.exe +++ b/src/main/resources/cx.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f73d920a720bd8c3a6f4f234a77589e51611f07645c53baf1650396f77ed4f3 -size 83032512 +oid sha256:5fb41bc7c8d5b3db18f4626d9523fc626fd19c6f88e1bb078184e7b6e2532fed +size 82995136