diff --git a/.github/workflows/pr-automation.yml b/.github/workflows/pr-automation.yml
deleted file mode 100644
index 2c5da62c..00000000
--- a/.github/workflows/pr-automation.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-# name: PR Automation
-# on:
-# pull_request_target:
-# types: [ready_for_review, opened, reopened]
-
-# permissions:
-# contents: none
-# issues: write
-# pull-requests: write
-
-# jobs:
-# add-reviewers:
-# runs-on: ubuntu-latest
-# if: ${{ github.event.pull_request.user.type != 'Bot' }}
-# steps:
-# - name: Request reviewers
-# env:
-# GH_REPO: ${{ github.repository }}
-# GH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
-# PRNUM: ${{ github.event.pull_request.number }}
-# PRAUTHOR: ${{ github.event.pull_request.user.login }}
-# run: gh pr edit $PRNUM
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 27426c7b..18845e81 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -139,8 +139,8 @@ jobs:
product_name: Java Wrapper
release_version: ${{ needs.release.outputs.TAG_NAME }}
cli_release_version: ${{ needs.release.outputs.CLI_VERSION }}
- release_author: "Phoenix Team"
- release_url: https://github.com/CheckmarxDev/ast-cli-java-wrapper/releases/tag/${{ needs.release.outputs.TAG_NAME }}
+ release_author: "Sypher Team"
+ release_url: https://github.com/Checkmarx/ast-cli-java-wrapper/releases/tag/${{ needs.release.outputs.TAG_NAME }}
jira_product_name: JAVA_WRAPPER
secrets: inherit
diff --git a/CODEOWNERS b/CODEOWNERS
index bb970776..7ba25c48 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -2,4 +2,4 @@
# Each line is a file pattern followed by one or more owners
# Specify the default owners for the entire repository
-* @cx-anurag-dalke @cx-anand-nandeshwar @cx-atish-jadhav
+#* @cx-anurag-dalke @cx-anand-nandeshwar @cx-atish-jadhav
diff --git a/README.md b/README.md
index 695249a3..d302f996 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-
+
AST-CLI-JAVA-WRAPPER
diff --git a/checkmarx-ast-cli.version b/checkmarx-ast-cli.version
index f7fb770b..a5774236 100644
--- a/checkmarx-ast-cli.version
+++ b/checkmarx-ast-cli.version
@@ -1 +1 @@
-2.3.28
+2.3.45
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..7a6a33a6
--- /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;
+ }
+}
\ 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
new file mode 100644
index 00000000..f9054ffe
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeResults.java
@@ -0,0 +1,53 @@
+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..cabe4b68
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/containersrealtime/ContainersRealtimeVulnerability.java
@@ -0,0 +1,24 @@
+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.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;
+
+ @JsonCreator
+ public ContainersRealtimeVulnerability(@JsonProperty("CVE") String cve,
+ @JsonProperty("Severity") String severity) {
+ 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
new file mode 100644
index 00000000..0afc103f
--- /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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/checkmarx/ast/mask/MaskResult.java b/src/main/java/com/checkmarx/ast/mask/MaskResult.java
new file mode 100644
index 00000000..80d9e8bb
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/mask/MaskResult.java
@@ -0,0 +1,37 @@
+package com.checkmarx.ast.mask;
+
+import com.checkmarx.ast.utils.JsonParser;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.Value;
+
+import java.util.List;
+
+@Value
+@EqualsAndHashCode()
+@JsonDeserialize()
+@ToString(callSuper = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class MaskResult {
+
+ List maskedSecrets;
+ String maskedFile;
+
+ @JsonCreator
+ public MaskResult(@JsonProperty("maskedSecrets") List maskedSecrets,
+ @JsonProperty("maskedFile") String maskedFile) {
+ this.maskedSecrets = maskedSecrets;
+ this.maskedFile = maskedFile;
+ }
+
+ public static MaskResult fromLine(String line) {
+ return JsonParser.parse(line, TypeFactory.defaultInstance().constructType(MaskResult.class));
+ }
+}
diff --git a/src/main/java/com/checkmarx/ast/mask/MaskedSecret.java b/src/main/java/com/checkmarx/ast/mask/MaskedSecret.java
new file mode 100644
index 00000000..a18e8827
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/mask/MaskedSecret.java
@@ -0,0 +1,32 @@
+package com.checkmarx.ast.mask;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.Value;
+
+@Value
+@EqualsAndHashCode()
+@JsonDeserialize()
+@ToString(callSuper = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class MaskedSecret {
+
+ String masked;
+ String secret;
+ int line;
+
+ @JsonCreator
+ public MaskedSecret(@JsonProperty("masked") String masked,
+ @JsonProperty("secret") String secret,
+ @JsonProperty("line") int line) {
+ this.masked = masked;
+ this.secret = secret;
+ this.line = line;
+ }
+}
diff --git a/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java
new file mode 100644
index 00000000..7141370f
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeResults.java
@@ -0,0 +1,55 @@
+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 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 OssRealtimeResults {
+ private static final Logger log = LoggerFactory.getLogger(OssRealtimeResults.class);
+
+ @JsonProperty("Packages") List packages;
+
+ @JsonCreator
+ public OssRealtimeResults(@JsonProperty("Packages") List packages) {
+ this.packages = packages == null ? Collections.emptyList() : 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 e) {
+ log.debug("Failed to parse oss 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/ossrealtime/OssRealtimeScanPackage.java b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java
new file mode 100644
index 00000000..d4b32771
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeScanPackage.java
@@ -0,0 +1,44 @@
+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;
+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 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 == null ? Collections.emptyList() : locations;
+ this.status = status;
+ 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
new file mode 100644
index 00000000..ac89b368
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/ossrealtime/OssRealtimeVulnerability.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 OssRealtimeVulnerability {
+ @JsonProperty("CVE") String cve;
+ @JsonProperty("Severity") String severity;
+ @JsonProperty("Description") String description;
+ @JsonProperty("FixVersion") String fixVersion;
+
+ @JsonCreator
+ public OssRealtimeVulnerability(@JsonProperty("CVE") String cve,
+ @JsonProperty("Severity") String severity,
+ @JsonProperty("Description") String description,
+ @JsonProperty("FixVersion") String fixVersion) {
+ this.cve = cve;
+ this.severity = severity;
+ this.description = description;
+ this.fixVersion = fixVersion;
+ }
+}
+
diff --git a/src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java b/src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java
new file mode 100644
index 00000000..277fe13d
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/realtime/RealtimeLocation.java
@@ -0,0 +1,28 @@
+package com.checkmarx.ast.realtime;
+
+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 RealtimeLocation {
+ @JsonProperty("Line") int line;
+ @JsonProperty("StartIndex") int startIndex;
+ @JsonProperty("EndIndex") int endIndex;
+
+ @JsonCreator
+ 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/results/result/Data.java b/src/main/java/com/checkmarx/ast/results/result/Data.java
index e9427663..35bda141 100644
--- a/src/main/java/com/checkmarx/ast/results/result/Data.java
+++ b/src/main/java/com/checkmarx/ast/results/result/Data.java
@@ -31,6 +31,10 @@ public class Data {
List nodes;
List packageData;
ScaPackageData scaPackageData;
+ // Secret Detection specific fields
+ String ruleName;
+ String ruleDescription;
+ String remediation;
public Data(@JsonProperty("queryId") String queryId,
@JsonProperty("queryName") String queryName,
@@ -47,7 +51,10 @@ public Data(@JsonProperty("queryId") String queryId,
@JsonProperty("line") int line,
@JsonProperty("nodes") List nodes,
@JsonProperty("packageData") List packageData,
- @JsonProperty("scaPackageData") ScaPackageData scaPackageData) {
+ @JsonProperty("scaPackageData") ScaPackageData scaPackageData,
+ @JsonProperty("ruleName") String ruleName,
+ @JsonProperty("ruleDescription") String ruleDescription,
+ @JsonProperty("remediation") String remediation) {
this.queryId = queryId;
this.queryName = queryName;
this.group = group;
@@ -64,5 +71,8 @@ public Data(@JsonProperty("queryId") String queryId,
this.nodes = nodes;
this.packageData = packageData;
this.scaPackageData = scaPackageData;
+ this.ruleName = ruleName;
+ this.ruleDescription = ruleDescription;
+ this.remediation = remediation;
}
}
diff --git a/src/main/java/com/checkmarx/ast/results/result/Result.java b/src/main/java/com/checkmarx/ast/results/result/Result.java
index 19686ab2..7217af2b 100644
--- a/src/main/java/com/checkmarx/ast/results/result/Result.java
+++ b/src/main/java/com/checkmarx/ast/results/result/Result.java
@@ -1,5 +1,6 @@
package com.checkmarx.ast.results.result;
+import com.checkmarx.ast.wrapper.CxConstants;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -53,7 +54,7 @@ public Result(@JsonProperty("type") String type,
@JsonProperty("comments") Comments comments,
@JsonProperty("vulnerabilityDetails") VulnerabilityDetails vulnerabilityDetails,
@JsonProperty("scaType") String scaType) {
- this.type = type;
+ this.type = normalizeType(type);
this.scaType=scaType;
this.label = label;
this.id = id;
@@ -74,4 +75,14 @@ public Result(@JsonProperty("type") String type,
this.comments = comments;
this.vulnerabilityDetails = vulnerabilityDetails;
}
+
+ /**
+ * Normalizes special-case types coming from JSON into internal constants.
+ */
+ private static String normalizeType(String rawType) {
+ if ("sscs-secret-detection".equals(rawType)) {
+ return CxConstants.SECRET_DETECTION;
+ }
+ return rawType; // leave other engine types unchanged
+ }
}
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..ef19050c
--- /dev/null
+++ b/src/main/java/com/checkmarx/ast/secretsrealtime/SecretsRealtimeResults.java
@@ -0,0 +1,94 @@
+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;
+
+ @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;
+ }
+ 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, Secret.class));
+ return new SecretsRealtimeResults(list);
+ }
+ if (trimmed.startsWith("{")) {
+ 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/CxConfig.java b/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java
index f23a5ea8..ab0a7329 100644
--- a/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java
+++ b/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java
@@ -16,7 +16,7 @@
public class CxConfig {
private static final Pattern pattern = Pattern.compile("([^\"]\\S*|\".+?\")\\s*");
-
+ private String agentName; //JETBRAINS
private String baseUri;
private String baseAuthUri;
private String tenant;
@@ -66,6 +66,10 @@ List toArguments() {
commands.add(CxConstants.BASE_AUTH_URI);
commands.add(getBaseAuthUri());
}
+ if (getAgentName() != null && !getAgentName().isEmpty()) {
+ commands.add("--agent");
+ commands.add(getAgentName());
+ }
if (getAdditionalParameters() != null)
commands.addAll(getAdditionalParameters());
diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java
index 889bbb65..ea2205ba 100644
--- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java
+++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java
@@ -13,6 +13,7 @@ public final class CxConstants {
public static final String AGENT = "--agent";
public static final String SAST = "sast";
public static final String DEBUG = "--debug";
+ public static final String SECRET_DETECTION = "scs";
static final String CLIENT_ID = "--client-id";
static final String CLIENT_SECRET = "--client-secret";
static final String API_KEY = "--apikey";
@@ -73,4 +74,37 @@ 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";
+ static final String DEV_ASSIST_LICENSE_KEY = "scan.config.plugins.cxdevassist";
+ static final String ONE_ASSIST_LICENSE_KEY = "scan.config.plugins.cxoneassist";
+ static final String IGNORED_FILE_PATH = "--ignored-file-path";
+ static final String SUB_CMD_OSS_REALTIME = "oss-realtime";
+ static final String SUB_CMD_IAC_REALTIME = "iac-realtime";
+ static final String SUB_CMD_SECRETS_REALTIME = "secrets-realtime";
+ static final String SUB_CMD_CONTAINERS_REALTIME = "containers-realtime";
+ static final String SUB_CMD_MASK = "mask";
+ static final String RESULT_FILE = "--result-file";
+ static final String CMD_TELEMETRY = "telemetry";
+ static final String SUB_CMD_TELEMETRY_AI = "ai";
+ static final String AI_PROVIDER = "--ai-provider";
+ static final String TYPE = "--type";
+ static final String SUB_TYPE = "--sub-type";
+ static final String PROBLEM_SEVERITY = "--problem-severity";
+ static final String SCAN_TYPE_FLAG = "--scan-type";
+ static final String STATUS = "--status";
+ static final String TOTAL_COUNT = "--total-count";
+ static final String DOCKER = "docker";
+ static final String PODMAN = "podman";
+ static final String PODMAN_FALLBACK_PATH = "/usr/local/bin/podman";
+ static final String DOCKER_FALLBACK_PATH = "/usr/local/bin/docker";
+
+ // Additional Docker fallback paths for macOS
+ // These paths cover various Docker installation methods on macOS:
+ // - Homebrew on Apple Silicon: /opt/homebrew/bin/docker
+ // - Docker Desktop CLI tools: ~/.docker/bin/docker (resolved at runtime)
+ // - Docker.app bundle: /Applications/Docker.app/Contents/Resources/bin/docker
+ // - Rancher Desktop: ~/.rd/bin/docker (resolved at runtime)
+ static final String DOCKER_HOMEBREW_PATH = "/opt/homebrew/bin/docker";
+ static final String DOCKER_APP_PATH = "/Applications/Docker.app/Contents/Resources/bin/docker";
+ static final String PODMAN_HOMEBREW_PATH = "/opt/homebrew/bin/podman";
}
diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java
index 38640d81..9e3c05f1 100644
--- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java
+++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java
@@ -4,6 +4,12 @@
import com.checkmarx.ast.codebashing.CodeBashing;
import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults;
import com.checkmarx.ast.learnMore.LearnMore;
+import com.checkmarx.ast.mask.MaskResult;
+import com.checkmarx.ast.ossrealtime.OssRealtimeResults;
+import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults;
+
+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;
@@ -23,18 +29,22 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
import java.io.IOException;
-import java.lang.reflect.Field;
import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+
+import static com.checkmarx.ast.wrapper.Execution.*;
public class CxWrapper {
private static final CollectionType BRANCHES_TYPE = TypeFactory.defaultInstance()
.constructCollectionType(List.class, String.class);
+ private static final String OS_LINUX = "linux";
+ private static final String OS_WINDOWS = "windows";
+ private static final String OS_MAC = "mac";
@NonNull
private final CxConfig cxConfig;
@@ -243,7 +253,7 @@ public List projectList(String filter) throws IOException, InterruptedE
return Execution.executeCommand(withConfigArguments(arguments), logger, Project::listFromLine);
}
- public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String agent) throws IOException, InterruptedException, CxException {
+ public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String agent, String ignoredFilePath) throws IOException, InterruptedException, CxException {
this.logger.info("Fetching ASCA scanResult");
List arguments = new ArrayList<>();
@@ -254,23 +264,27 @@ public ScanResult ScanAsca(String fileSource, boolean ascaLatestVersion, String
if (ascaLatestVersion) {
arguments.add(CxConstants.ASCA_LATEST_VERSION);
}
+ if (StringUtils.isNotBlank(ignoredFilePath)) {
+ arguments.add(CxConstants.IGNORED_FILE_PATH);
+ arguments.add(ignoredFilePath);
+ }
+
- appendAgentToArguments(agent, arguments);
return Execution.executeCommand(withConfigArguments(arguments), logger, ScanResult::fromLine,
(args, ignored) ->
(args.size() >= 3 && args.get(1).equals(CxConstants.CMD_SCAN) && args.get(2).equals(CxConstants.SUB_CMD_ASCA)));
}
- private static void appendAgentToArguments(String agent, List arguments) {
- arguments.add(CxConstants.AGENT);
- if (agent != null && !agent.isEmpty()){
- arguments.add(agent);
- }
- else{
- arguments.add("CLI-Java-Wrapper");
- }
- }
+ // private static void appendAgentToArguments(String agent, List arguments) {
+ // arguments.add(CxConstants.AGENT);
+ // if (agent != null && !agent.isEmpty()){
+ // arguments.add(agent);
+ // }
+ // else{
+ // arguments.add("CLI-Java-Wrapper");
+ // }
+ // }
public List projectBranches(@NonNull UUID projectId, String filter)
throws CxException, IOException, InterruptedException {
@@ -340,10 +354,6 @@ public String results(@NonNull UUID scanId, ReportFormat reportFormat, String ag
arguments.add(fileName);
arguments.add(CxConstants.OUTPUT_PATH);
arguments.add(tempDir);
- if (agent != null) {
- arguments.add(CxConstants.AGENT);
- arguments.add(agent);
- }
return Execution.executeCommand(arguments,
logger, tempDir,
fileName + reportFormat.getExtension());
@@ -396,7 +406,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);
}
@@ -404,6 +414,192 @@ public KicsRealtimeResults kicsRealtimeScan(@NonNull String fileSources, String
return Execution.executeCommand(withConfigArguments(arguments), logger, KicsRealtimeResults::fromLine);
}
+ public String checkEngineExist(@NonNull String engineName) throws CxException, IOException, InterruptedException {
+ String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
+ String osType=Execution.getOperatingSystemType(osName);
+ return this.checkEngine(engineName,osType);
+ }
+
+ private String verifyEngineOnMAC(String engineName,Listarguments) throws CxException, IOException, InterruptedException {
+ this.logger.debug("Verifying container engine '{}' on macOS", engineName);
+
+ // First, try to find the engine via shell command (works when launched from terminal)
+ try{
+ String enginePath = Execution.executeCommand((arguments), logger, line->line);
+ if (enginePath != null && !enginePath.isEmpty()) {
+ this.logger.debug("Found engine '{}' via shell command: {}", engineName, enginePath);
+ return enginePath;
+ }
+ } catch (CxException | IOException e) {
+ this.logger.debug("Shell command lookup failed for '{}': {}", engineName, e.getMessage());
+ }
+
+ // Build list of fallback paths based on engine type
+ // This handles the case when IntelliJ is launched via GUI (double-click) and doesn't inherit shell PATH
+ List fallbackPaths = new ArrayList<>();
+ if (CxConstants.DOCKER.equalsIgnoreCase(engineName)) {
+ fallbackPaths.add(CxConstants.DOCKER_FALLBACK_PATH); // /usr/local/bin/docker
+ fallbackPaths.add(CxConstants.DOCKER_HOMEBREW_PATH); // /opt/homebrew/bin/docker (Apple Silicon)
+ fallbackPaths.add(CxConstants.DOCKER_APP_PATH); // /Applications/Docker.app/Contents/Resources/bin/docker
+ // Add user home-based paths
+ String userHome = System.getProperty("user.home");
+ if (userHome != null) {
+ fallbackPaths.add(userHome + "/.docker/bin/docker"); // Docker Desktop CLI
+ fallbackPaths.add(userHome + "/.rd/bin/docker"); // Rancher Desktop
+ }
+ } else if (CxConstants.PODMAN.equalsIgnoreCase(engineName)) {
+ fallbackPaths.add(CxConstants.PODMAN_FALLBACK_PATH); // /usr/local/bin/podman
+ fallbackPaths.add(CxConstants.PODMAN_HOMEBREW_PATH); // /opt/homebrew/bin/podman (Apple Silicon)
+ // Add user home-based paths
+ String userHome = System.getProperty("user.home");
+ if (userHome != null) {
+ fallbackPaths.add(userHome + "/.local/bin/podman");
+ }
+ }
+
+ this.logger.debug("Checking {} fallback paths for engine '{}'", fallbackPaths.size(), engineName);
+
+ // Try each fallback path
+ for (String pathStr : fallbackPaths) {
+ Path path = Paths.get(pathStr);
+ this.logger.debug("Checking fallback path: {}", pathStr);
+
+ if (Files.exists(path)) {
+ String resolvedPath;
+ try {
+ if (Files.isSymbolicLink(path)) {
+ resolvedPath = Files.readSymbolicLink(path).toAbsolutePath().toString();
+ this.logger.debug("Resolved symlink {} -> {}", pathStr, resolvedPath);
+ } else {
+ resolvedPath = path.toAbsolutePath().toString();
+ }
+
+ // Verify the engine is executable and works
+ if (verifyEngineExecutable(resolvedPath)) {
+ this.logger.info("Found working container engine '{}' at: {}", engineName, resolvedPath);
+ return resolvedPath;
+ }
+ } catch (IOException e) {
+ this.logger.debug("Failed to resolve path {}: {}", pathStr, e.getMessage());
+ }
+ } else {
+ this.logger.debug("Path does not exist: {}", pathStr);
+ }
+ }
+
+ throw new CxException(
+ 1, engineName + " is not installed or is not accessible from the system PATH."
+ );
+ }
+
+ /**
+ * Verifies that the engine at the given path is executable and responds to --version.
+ *
+ * @param enginePath the absolute path to the container engine executable
+ * @return true if the engine is working, false otherwise
+ */
+ private boolean verifyEngineExecutable(String enginePath) {
+ try {
+ Path path = Paths.get(enginePath);
+ if (!Files.exists(path) || !Files.isExecutable(path)) {
+ this.logger.debug("Engine path '{}' is not executable", enginePath);
+ return false;
+ }
+
+ // Run a quick version check to verify the engine works
+ ProcessBuilder pb = new ProcessBuilder(enginePath, "--version");
+ pb.redirectErrorStream(true);
+ Process process = pb.start();
+ boolean completed = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ if (completed && process.exitValue() == 0) {
+ this.logger.debug("Engine at '{}' verified successfully", enginePath);
+ return true;
+ } else {
+ this.logger.debug("Engine at '{}' failed verification (exit code: {}, completed: {})",
+ enginePath, process.exitValue(), completed);
+ if (!completed) {
+ process.destroyForcibly();
+ }
+ return false;
+ }
+ } catch (Exception e) {
+ this.logger.debug("Engine verification failed for '{}': {}", enginePath, e.getMessage());
+ return false;
+ }
+ }
+
+ private String checkEngine(String engineName, String osType ) throws CxException, IOException, InterruptedException {
+ List arguments = new ArrayList<>();
+ switch (osType){
+ case OS_MAC:
+ arguments.add("/bin/sh");
+ arguments.add("-c");
+ arguments.add("command -v " + engineName);
+ return verifyEngineOnMAC(engineName,arguments);
+ case OS_WINDOWS:
+ case OS_LINUX:
+ arguments.add(engineName);
+ arguments.add("--version");
+ try {
+ Execution.executeCommand(arguments, logger, line -> line);
+ return engineName;
+ } catch (CxException | IOException e) {
+ throw new CxException(
+ 1,engineName+" is not installed or is not accessible from the system PATH."
+ );
+ }
+ default:
+ throw new IllegalArgumentException("Unsupported OS: " + osType);
+ }
+
+ }
+
+ public T realtimeScan(@NonNull String subCommand, @NonNull String sourcePath, String containerTool, String ignoredFilePath, java.util.function.Function resultParser)
+ throws IOException, InterruptedException, CxException {
+ this.logger.info("Executing 'scan {}' command using the CLI.", subCommand);
+ this.logger.info("Source: {} IgnoredFilePath: {}", sourcePath, ignoredFilePath);
+ List arguments = new ArrayList<>();
+ arguments.add(CxConstants.CMD_SCAN);
+ arguments.add(subCommand);
+ arguments.add(CxConstants.SOURCE);
+ arguments.add(sourcePath);
+ if(StringUtils.isNotBlank(containerTool)){
+ arguments.add(CxConstants.ENGINE);
+ arguments.add(containerTool);
+ }
+ if (StringUtils.isNotBlank(ignoredFilePath)) {
+ arguments.add(CxConstants.IGNORED_FILE_PATH);
+ arguments.add(ignoredFilePath);
+ }
+ 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);
+ }
+
+ // IAC Realtime
+ public IacRealtimeResults iacRealtimeScan(@NonNull String sourcePath,String containerTool, String ignoredFilePath)
+ throws IOException, InterruptedException, CxException {
+ return realtimeScan(CxConstants.SUB_CMD_IAC_REALTIME, sourcePath,containerTool, ignoredFilePath, IacRealtimeResults::fromLine);
+ }
+
+
+ // Secrets Realtime
+ public SecretsRealtimeResults secretsRealtimeScan(@NonNull String sourcePath, String ignoredFilePath)
+ throws IOException, InterruptedException, CxException {
+ return realtimeScan(CxConstants.SUB_CMD_SECRETS_REALTIME, sourcePath,"", ignoredFilePath, SecretsRealtimeResults::fromLine);
+ }
+
+ // Containers Realtime
+ public ContainersRealtimeResults containersRealtimeScan(@NonNull String sourcePath, String ignoredFilePath)
+ throws IOException, InterruptedException, CxException {
+ return realtimeScan(CxConstants.SUB_CMD_CONTAINERS_REALTIME, sourcePath, "",ignoredFilePath, ContainersRealtimeResults::fromLine);
+ }
+
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.");
@@ -418,11 +614,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);
}
@@ -445,7 +641,7 @@ public List learnMore(String queryId) throws CxException, IOException
public boolean ideScansEnabled() throws CxException, IOException, InterruptedException {
List tenantSettings = tenantSettings();
- if (tenantSettings == null) {
+ if (tenantSettings == null || tenantSettings.isEmpty()) {
throw new CxException(1, "Unable to parse tenant settings");
}
return tenantSettings.stream()
@@ -455,6 +651,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();
@@ -464,6 +672,85 @@ public List tenantSettings() throws CxException, IOException, Int
return Execution.executeCommand(withConfigArguments(arguments), logger, TenantSetting::listFromLine);
}
+
+
+ public boolean getTenantSetting(String key) throws CxException, IOException, InterruptedException {
+ List tenantSettings = tenantSettings();
+ if (tenantSettings == null) {
+ throw new CxException(1, "Unable to parse tenant settings");
+ }
+ return tenantSettings.stream()
+ .filter(t -> t.getKey().equals(key))
+ .findFirst()
+ .map(t -> Boolean.parseBoolean(t.getValue()))
+ .orElse(false);
+ }
+ public boolean devAssistEnabled() throws CxException, IOException, InterruptedException {
+ return getTenantSetting(CxConstants.DEV_ASSIST_LICENSE_KEY);
+
+ }
+
+ public boolean oneAssistEnabled() throws CxException, IOException, InterruptedException {
+ return getTenantSetting(CxConstants.ONE_ASSIST_LICENSE_KEY);
+ }
+
+ public MaskResult maskSecrets(@NonNull String filePath) throws CxException, IOException, InterruptedException {
+ List arguments = new ArrayList<>();
+
+ arguments.add(CxConstants.CMD_UTILS);
+ arguments.add(CxConstants.SUB_CMD_MASK);
+ arguments.add(CxConstants.RESULT_FILE);
+ arguments.add(filePath);
+
+ return Execution.executeCommand(withConfigArguments(arguments), logger, MaskResult::fromLine);
+ }
+
+ /**
+ * Executes telemetry AI command to collect telemetry data for user interactions related to AI features.
+ *
+ * @param aiProvider AI provider name (e.g., "Copilot")
+ * @param agent Agent name (e.g., "Jetbrains")
+ * @param eventType Event type (e.g., "click")
+ * @param subType Event subtype (e.g., "ast-results.viewPackageDetails")
+ * @param engine Engine type (e.g., "secrets")
+ * @param problemSeverity Severity level (e.g., "high")
+ * @param scanType Type of scan
+ * @param status Status information
+ * @param totalCount Number count
+ * @return Command output as string
+ * @throws IOException if I/O error occurs
+ * @throws InterruptedException if command execution is interrupted
+ * @throws CxException if CLI command fails
+ */
+ public String telemetryAIEvent(String aiProvider, String agent, String eventType, String subType,
+ String engine, String problemSeverity, String scanType, String status,
+ Integer totalCount) throws IOException, InterruptedException, CxException {
+ this.logger.info("Executing telemetry AI event with provider: {}, type: {}, subType: {}",
+ aiProvider, eventType, subType);
+
+ List arguments = new ArrayList<>();
+ arguments.add(CxConstants.CMD_TELEMETRY);
+ arguments.add(CxConstants.SUB_CMD_TELEMETRY_AI);
+ arguments.add(CxConstants.AI_PROVIDER);
+ arguments.add(aiProvider);
+ arguments.add(CxConstants.TYPE);
+ arguments.add(eventType);
+ arguments.add(CxConstants.SUB_TYPE);
+ arguments.add(subType);
+ arguments.add(CxConstants.ENGINE);
+ arguments.add(engine);
+ arguments.add(CxConstants.PROBLEM_SEVERITY);
+ arguments.add(problemSeverity);
+ arguments.add(CxConstants.SCAN_TYPE_FLAG);
+ arguments.add(scanType);
+ arguments.add(CxConstants.STATUS);
+ arguments.add(status);
+ arguments.add(CxConstants.TOTAL_COUNT);
+ arguments.add(totalCount.toString());
+
+ return Execution.executeCommand(withConfigArguments(arguments), logger, line -> line);
+ }
+
private int getIndexOfBfLNode(List bflNodes, List resultNodes) {
int bflNodeNotFound = -1;
diff --git a/src/main/java/com/checkmarx/ast/wrapper/Execution.java b/src/main/java/com/checkmarx/ast/wrapper/Execution.java
index 9d45cac3..0a888ec0 100644
--- a/src/main/java/com/checkmarx/ast/wrapper/Execution.java
+++ b/src/main/java/com/checkmarx/ast/wrapper/Execution.java
@@ -1,5 +1,6 @@
package com.checkmarx.ast.wrapper;
+import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults;
import lombok.NonNull;
import org.slf4j.Logger;
@@ -12,10 +13,7 @@
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
+import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -57,7 +55,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 +96,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();
@@ -171,7 +169,7 @@ private static String detectBinaryName(@NonNull Logger logger) {
return fileName;
}
- private static String getOperatingSystemType(String osName) {
+ public static String getOperatingSystemType(String osName) {
if (osName.contains(OS_LINUX)) {
return OS_LINUX;
} else if (osName.contains(OS_WINDOWS)) {
@@ -217,4 +215,6 @@ private static String md5(InputStream a) {
}
return md5;
}
+
+
}
diff --git a/src/main/resources/cx-linux b/src/main/resources/cx-linux
index 22c6f030..c8b2d1e6 100755
--- a/src/main/resources/cx-linux
+++ b/src/main/resources/cx-linux
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1d2436e1fef7dd4841a003697702e52e2cdc3f3fba74e2196cebe5d7992f9d4f
-size 76206264
+oid sha256:18b7c2808d4513ba9ed1041c48eba1da4ced2f644cd03f223790dadc0739b3b4
+size 81105080
diff --git a/src/main/resources/cx-linux-arm b/src/main/resources/cx-linux-arm
index b7b9337d..cd70ab03 100755
--- a/src/main/resources/cx-linux-arm
+++ b/src/main/resources/cx-linux-arm
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e41b066ccc5dd4281a134518b3ece5ac6c17f229c9a7844493371ac3936a94a4
-size 72745144
+oid sha256:f24c5b06bf7705376d34420443f3e05dfdd1e5da78cdbff451fec93a686f1f31
+size 77398200
diff --git a/src/main/resources/cx-mac b/src/main/resources/cx-mac
index e73d13f2..6ca3efbe 100755
--- a/src/main/resources/cx-mac
+++ b/src/main/resources/cx-mac
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4764f25769b820906cf09c6b3281f2b1df7c021f9063aef983f2aac0c48e39bc
-size 153324992
+oid sha256:886568c8a6d83b8a56d6a875fa8b356fd3cb5a4902e9023b0c68ba25d87e00bb
+size 163157360
diff --git a/src/main/resources/cx.exe b/src/main/resources/cx.exe
index 82fad51a..6eea9b98 100644
--- a/src/main/resources/cx.exe
+++ b/src/main/resources/cx.exe
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7968c2c1ec4e6264e91324f649f2bb44db04e53ce20f90f3a77c56abf2525664
-size 78184384
+oid sha256:95de44ce585c74602f12e129f67b7a0c9c9b789d469f9338e655d5f2fa069f28
+size 83068864
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/MaskTest.java b/src/test/java/com/checkmarx/ast/MaskTest.java
new file mode 100644
index 00000000..ad188d21
--- /dev/null
+++ b/src/test/java/com/checkmarx/ast/MaskTest.java
@@ -0,0 +1,105 @@
+package com.checkmarx.ast;
+
+import com.checkmarx.ast.mask.MaskResult;
+import com.checkmarx.ast.mask.MaskedSecret;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class MaskTest extends BaseTest {
+
+ private static final String RESULTS_FILE = "target/test-classes/results.json";
+ private static final String SECRETS_REALTIME_FILE = "target/test-classes/Secrets-realtime.json";
+
+ @Test
+ void testMaskSecretsWithFileContainingSecrets() throws Exception {
+ // Tests CLI execution with file containing actual secrets and validates masking behavior
+ MaskResult result = wrapper.maskSecrets(SECRETS_REALTIME_FILE);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertNotNull(result.getMaskedFile());
+ Assertions.assertNotNull(result.getMaskedSecrets());
+ Assertions.assertFalse(result.getMaskedSecrets().isEmpty());
+
+ MaskedSecret secret = result.getMaskedSecrets().get(0);
+ Assertions.assertNotNull(secret.getMasked());
+ Assertions.assertNotNull(secret.getSecret());
+ Assertions.assertEquals(5, secret.getLine());
+ Assertions.assertTrue(secret.getMasked().contains("") || secret.getMasked().contains("\\u003cmasked\\u003e"));
+ Assertions.assertTrue(secret.getSecret().contains("-----BEGIN RSA PRIVATE KEY-----"));
+ Assertions.assertTrue(secret.getSecret().length() > secret.getMasked().length());
+ }
+
+ @Test
+ void testMaskSecretsWithFileContainingNoSecrets() throws Exception {
+ // Tests CLI execution with file containing no secrets
+ MaskResult result = wrapper.maskSecrets(RESULTS_FILE);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertNotNull(result.getMaskedFile());
+ Assertions.assertFalse(result.getMaskedFile().isEmpty());
+ }
+
+ @Test
+ void testMaskSecretsErrorHandling() {
+ // Tests CLI error handling for invalid inputs
+ Assertions.assertThrows(Exception.class, () -> wrapper.maskSecrets(null));
+ Assertions.assertThrows(Exception.class, () -> wrapper.maskSecrets("non-existent-file.json"));
+ Assertions.assertDoesNotThrow(() -> wrapper.maskSecrets(RESULTS_FILE));
+ }
+
+ @Test
+ void testMaskSecretsResponseParsing() throws Exception {
+ // Tests CLI response structure and JSON parsing functionality
+ MaskResult result = wrapper.maskSecrets(SECRETS_REALTIME_FILE);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertNotNull(result.getMaskedSecrets());
+ Assertions.assertFalse(result.getMaskedSecrets().isEmpty());
+
+ MaskedSecret secret = result.getMaskedSecrets().get(0);
+ Assertions.assertNotNull(secret.getMasked());
+ Assertions.assertNotNull(secret.getSecret());
+ Assertions.assertTrue(secret.getLine() >= 0);
+
+ Assertions.assertNull(MaskResult.fromLine(""));
+ Assertions.assertNull(MaskResult.fromLine("{invalid json}"));
+ Assertions.assertNull(MaskResult.fromLine(null));
+ }
+
+ @Test
+ void testMaskSecretsObjectBehavior() throws Exception {
+ // Tests object equality, serialization and consistency with CLI responses
+ MaskResult result1 = wrapper.maskSecrets(SECRETS_REALTIME_FILE);
+ MaskResult result2 = wrapper.maskSecrets(SECRETS_REALTIME_FILE);
+
+ Assertions.assertEquals(result1.getMaskedFile(), result2.getMaskedFile());
+ Assertions.assertNotNull(result1.toString());
+ Assertions.assertTrue(result1.toString().contains("MaskResult"));
+
+ if (result1.getMaskedSecrets() != null && !result1.getMaskedSecrets().isEmpty()) {
+ MaskedSecret secret1 = result1.getMaskedSecrets().get(0);
+ MaskedSecret secret2 = result2.getMaskedSecrets().get(0);
+
+ Assertions.assertEquals(secret1.getMasked(), secret2.getMasked());
+ Assertions.assertEquals(secret1.getSecret(), secret2.getSecret());
+ Assertions.assertEquals(secret1.getLine(), secret2.getLine());
+ Assertions.assertEquals(secret1.hashCode(), secret2.hashCode());
+ Assertions.assertEquals(secret1, secret1);
+ Assertions.assertNotEquals(secret1, null);
+
+ String toString = secret1.toString();
+ Assertions.assertNotNull(toString);
+ Assertions.assertTrue(toString.contains("MaskedSecret"));
+ }
+
+ ObjectMapper mapper = new ObjectMapper();
+ String json = mapper.writeValueAsString(result1);
+ MaskResult deserialized = mapper.readValue(json, MaskResult.class);
+
+ Assertions.assertEquals(result1.getMaskedFile(), deserialized.getMaskedFile());
+ if (result1.getMaskedSecrets() != null) {
+ Assertions.assertEquals(result1.getMaskedSecrets().size(), deserialized.getMaskedSecrets().size());
+ }
+ }
+}
diff --git a/src/test/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/ScanTest.java b/src/test/java/com/checkmarx/ast/ScanTest.java
index fb0b3b3b..a414281e 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;
@@ -23,7 +25,7 @@ void testScanShow() throws Exception {
@Test
void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulResponseWithCorrectValues() throws Exception {
- ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode");
+ ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode", null);
// Assertions for the scan result
Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null");
@@ -44,7 +46,7 @@ void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulRes
@Test
void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseWithCorrectValues() throws Exception {
- ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null);
+ ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null, null);
Assertions.assertNotNull(scanResult.getRequestId());
Assertions.assertTrue(scanResult.isStatus());
Assertions.assertNull(scanResult.getError());
@@ -53,12 +55,25 @@ void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseW
@Test
void testScanAsca_WhenMissingFileExtension_ReturnFileExtensionIsRequiredFailure() throws Exception {
- ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null);
+ ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null, null);
Assertions.assertNotNull(scanResult.getRequestId());
Assertions.assertNotNull(scanResult.getError());
Assertions.assertEquals("The file name must have an extension.", scanResult.getError().getDescription());
}
+ @Test
+ void testScanAsca_WithIgnoreFilePath_ShouldWorkCorrectly() throws Exception {
+ String ignoreFile = "src/test/resources/ignored-packages.json";
+
+ // Test with ignore file - should not break the scanning process
+ ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "test-agent", ignoreFile);
+
+ // Verify the scan completes successfully
+ Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null");
+ Assertions.assertTrue(scanResult.isStatus(), "Status should be true");
+ Assertions.assertNull(scanResult.getError(), "Error should be null when scan is successful");
+ }
+
@Test
void testScanList() throws Exception {
List cxOutput = wrapper.scanList("limit=10");
@@ -92,4 +107,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/SecretsRealtimeResultsTest.java b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java
new file mode 100644
index 00000000..a662d0f1
--- /dev/null
+++ b/src/test/java/com/checkmarx/ast/SecretsRealtimeResultsTest.java
@@ -0,0 +1,293 @@
+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/TelemetryTest.java b/src/test/java/com/checkmarx/ast/TelemetryTest.java
new file mode 100644
index 00000000..a014aa65
--- /dev/null
+++ b/src/test/java/com/checkmarx/ast/TelemetryTest.java
@@ -0,0 +1,67 @@
+package com.checkmarx.ast;
+
+import com.checkmarx.ast.wrapper.CxException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+/**
+ * Telemetry AI event test cases covering various parameter scenarios.
+ */
+class TelemetryTest extends BaseTest {
+
+ @Test
+ void testTelemetryAIEventSuccessfulCaseWithMinimalParametersAiLog() throws CxException, IOException, InterruptedException {
+ // Test case: AI logging with specific parameters and some empty values
+ Assertions.assertDoesNotThrow(() -> {
+ String result = wrapper.telemetryAIEvent(
+ "Cursor", // aiProvider
+ "Cursos", // agent
+ "click", // eventType
+ "ast-results.viewPackageDetails", // subType
+ "secrets", // engine
+ "high", // problemSeverity
+ "", // scanType (empty)
+ "", // status (empty)
+ 0 // totalCount
+ );
+ }, "Telemetry AI event should execute successfully");
+ }
+
+ @Test
+ void testTelemetryAIEventSuccessfulCaseWithMinimalParametersDetectionLog() throws CxException, IOException, InterruptedException {
+ // Test case: Detection logging with most parameters empty and specific scan data
+ Assertions.assertDoesNotThrow(() -> {
+ String result = wrapper.telemetryAIEvent(
+ "", // aiProvider (empty)
+ "", // agent (empty)
+ "", // eventType (empty)
+ "", // subType (empty)
+ "", // engine (empty)
+ "", // problemSeverity (empty)
+ "asca", // scanType
+ "Critical", // status
+ 10 // totalCount
+ );
+ }, "Telemetry AI event should execute successfully for detection log");
+ }
+
+ @Test
+ void testTelemetryAIEventSuccessfulCaseWithEdgeCaseParameters() throws CxException, IOException, InterruptedException {
+ // Test case: Edge case with minimal required parameters
+ Assertions.assertDoesNotThrow(() -> {
+ String result = wrapper.telemetryAIEvent(
+ "test-provider", // aiProvider (minimal value)
+ "java-wrapper", // agent (minimal value)
+ "", // eventType (empty)
+ "", // subType (empty)
+ "", // engine (empty)
+ "", // problemSeverity (empty)
+ "", // scanType (empty)
+ "", // status (empty)
+ 0 // totalCount
+ );
+ }, "Telemetry AI event should execute successfully for edge case");
+ }
+}
diff --git a/src/test/java/com/checkmarx/ast/TenantTest.java b/src/test/java/com/checkmarx/ast/TenantTest.java
index b9ac752c..91824f4e 100644
--- a/src/test/java/com/checkmarx/ast/TenantTest.java
+++ b/src/test/java/com/checkmarx/ast/TenantTest.java
@@ -11,11 +11,27 @@ 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");
+ }
+
+ @Test
+ void testDevAssistEnabled() {
+ Assertions.assertDoesNotThrow(() -> wrapper.devAssistEnabled());
+ }
+
+ @Test
+ void testOneAssistEnabled() {
+ Assertions.assertDoesNotThrow(() -> wrapper.oneAssistEnabled());
+ }
}
diff --git a/src/test/resources/Secrets-realtime.json b/src/test/resources/Secrets-realtime.json
new file mode 100644
index 00000000..5acdd938
--- /dev/null
+++ b/src/test/resources/Secrets-realtime.json
@@ -0,0 +1,147 @@
+[
+ {
+ "Title": "private-key",
+ "Description": "Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.",
+ "SecretValue": "-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAl5X22d9tXl2Bz1b+3mWsAouoBiQhrDS3GxFAdpJFkKF6Wst+Vl1mfshTd+gF2kHXTzLMdxsUM2AS8laG2xeIhLe07FhhOtQGSoAOjHD++K53MBcOD/mDVOlPhNOWAc3qWfa3R7ohxUJq8lvy1OErw7qlQv9U+xABUJtHbJtMn/dDBs1Fy6MUgO6TOtEwzQaTaGpWh3NmGaUu3KQuaekHZnzlYd8mc1XkztXrph0XxZPq43Gg8RVh25fCrcA+7iAkqa4MNL/5gsatzzjP7KRdx1IP0pnM6F/cwoMClmuV7FFhKoP3KFwt8SXglZnqLrCNK6DzrYU5bRaszIxYc8egywIDAQABAoIBADGaq2rkiF+m7cGx0Dlqv/0dQmCwFizKG1lKLfQfLZCEpwtrJ+6PJek7GMVWMgQYI6MRFoOrYtLlD44p7ntnmg8EJrpouXiMxXo/qYMfvvAV937PLJThq65vosvuiVoRziyeZZ+dM0vfzit9F1u+S5oDS+0+rMpzlFqSVa8eqtZ1i4wmVwKXF35FqHyzhXgKmVNXUy/JCiftJYF2hpe2zXAuGQKTD5x3v52GX+cUsRPPAO5GhJbDnwhbL8i06gLDc7xwfaTJbebvOKHT1F2AW/RhoW+BpJke86beea8DM7KR8RYjobgdf66fadosa27u8qqpcdtyzYMqLc3GuTeYL/ECgYEA36KgaxlWNCuQhLOLPZ/2fzebD1SCI7HsPtbiM+mSbWTGBnSPX36b1XLJkynZrAFKYoe9zx9ksqlrBHWG7PC3FLeIO9ExV9BsGFGjzDpkENteArcO5eLfSMkfXIXNlWwhb8m8DPjdnK2pihs3vVN0OMIPeYu9mAXst0CcR8vQuJ0CgYEArYYBgmAyWfRs8exU2F7vQoG8B4mykTOQ9J91Js72WiHaqt+z4NdGJrFr5VkXCZdbBf3J/PfEPVFT3ud/dAm2PZ40TfNQJTa4pwBhuyCozZff1Qm+X5NdzvHkLePFex6wxUgupVwr/W9UOVU7gRFB/hziSLnlglfpgwxA9j9tfocCgYEAqH69YzQp0RDpyDIGvR2i+WMJ/1jq3L4Xg5kfwYFAhA+jbAWyaH7aJs5ftfOYP5KRWv9vMXkzw7EGIsvyJt+O8Zr+mCMbjFBKwV/xi9SKxHCjumP2Y5q2JP70FB/0L5rS7okOmK+BOaVW0emD66/PJ1x/kFKLPNlp6wBRP37++bkCgYAgrkdkfaeeB4npOmB0a9TWCscWCFoIPNUFLW8MAxikuxGK8xzWsNS2ft3aUSAkn0v2YekD6sob3lBUf/ciLJ4VFtG1CKlEiPzX/xto+eqw5fSzE+W17HRTgH1AI1DTMmGKlmCqpiRm0+vh7GqLkWuDZ386wUA3f0UseEdX2XROywKBgE1Xer2yEZtLlrubHgVAKVrz2u5ZCEPzDkLEDhOxX2h1dP19TWvm2Sr6Fm1QL8lez52YhCW/xJHsr8S4Kka3Ntbm9+ZfhhATTICTpqIicqeAq/Iiw++7UgCk1gZPW1hnlDHYRdmI4Dr6j5aUBFLl4Bj2nedH+1L1Eo8EXfnjm0pi-----END RSA PRIVATE KEY-----",
+ "FilePath": "C:\\Users\\XYZ\\GitHub_Repo\\JavaVulnerableLab\\CxAppMonDeploy.pem",
+ "Severity": "High",
+ "Locations": [
+ {
+ "Line": 0,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 1,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 2,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 3,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 4,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 5,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 6,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 7,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 8,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 9,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 10,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 11,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 12,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 13,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 14,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 15,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 16,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 17,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 18,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 19,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 20,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 21,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 22,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 23,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 24,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 25,
+ "StartIndex": 1,
+ "EndIndex": 30
+ },
+ {
+ "Line": 26,
+ "StartIndex": 1,
+ "EndIndex": 30
+ }
+ ]
+ }
+]
+
diff --git a/src/test/resources/ignored-packages.json b/src/test/resources/ignored-packages.json
new file mode 100644
index 00000000..5ec153d5
--- /dev/null
+++ b/src/test/resources/ignored-packages.json
@@ -0,0 +1,5 @@
+[
+ {"name": "jackson-databind", "version": "*"},
+ {"name": "commons-lang3", "version": "*"},
+ {"name": "json-simple", "version": "*"}
+]