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 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] 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;" - } -} -