diff --git a/README.md b/README.md
index 695249a..d302f99 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 e41ae21..9f56377 100644
--- a/checkmarx-ast-cli.version
+++ b/checkmarx-ast-cli.version
@@ -1 +1 @@
-2.3.42
+2.3.44
diff --git a/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java b/src/main/java/com/checkmarx/ast/wrapper/CxConfig.java
index f23a5ea..ab0a732 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 79f171c..ea2205b 100644
--- a/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java
+++ b/src/main/java/com/checkmarx/ast/wrapper/CxConstants.java
@@ -75,6 +75,8 @@ public final class CxConstants {
static final String SUB_CMD_TENANT = "tenant";
static final String IDE_SCANS_KEY = "scan.config.plugins.ideScans";
static final String AI_MCP_SERVER_KEY = "scan.config.plugins.aiMcpServer";
+ static final String DEV_ASSIST_LICENSE_KEY = "scan.config.plugins.cxdevassist";
+ static final String ONE_ASSIST_LICENSE_KEY = "scan.config.plugins.cxoneassist";
static final String IGNORED_FILE_PATH = "--ignored-file-path";
static final String SUB_CMD_OSS_REALTIME = "oss-realtime";
static final String SUB_CMD_IAC_REALTIME = "iac-realtime";
@@ -91,4 +93,18 @@ public final class CxConstants {
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 386133a..9e3c05f 100644
--- a/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java
+++ b/src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java
@@ -29,17 +29,22 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.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;
@@ -248,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<>();
@@ -259,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 {
@@ -345,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());
@@ -409,6 +414,147 @@ 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);
@@ -495,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()
@@ -526,6 +672,28 @@ public List tenantSettings() throws CxException, IOException, Int
return Execution.executeCommand(withConfigArguments(arguments), logger, TenantSetting::listFromLine);
}
+
+
+ public boolean getTenantSetting(String key) throws CxException, IOException, InterruptedException {
+ List tenantSettings = tenantSettings();
+ if (tenantSettings == null) {
+ throw new CxException(1, "Unable to parse tenant settings");
+ }
+ return tenantSettings.stream()
+ .filter(t -> t.getKey().equals(key))
+ .findFirst()
+ .map(t -> Boolean.parseBoolean(t.getValue()))
+ .orElse(false);
+ }
+ public boolean devAssistEnabled() throws CxException, IOException, InterruptedException {
+ return getTenantSetting(CxConstants.DEV_ASSIST_LICENSE_KEY);
+
+ }
+
+ public boolean oneAssistEnabled() throws CxException, IOException, InterruptedException {
+ return getTenantSetting(CxConstants.ONE_ASSIST_LICENSE_KEY);
+ }
+
public MaskResult maskSecrets(@NonNull String filePath) throws CxException, IOException, InterruptedException {
List arguments = new ArrayList<>();
@@ -565,8 +733,6 @@ public String telemetryAIEvent(String aiProvider, String agent, String eventType
arguments.add(CxConstants.SUB_CMD_TELEMETRY_AI);
arguments.add(CxConstants.AI_PROVIDER);
arguments.add(aiProvider);
- arguments.add(CxConstants.AGENT);
- arguments.add(agent);
arguments.add(CxConstants.TYPE);
arguments.add(eventType);
arguments.add(CxConstants.SUB_TYPE);
diff --git a/src/main/java/com/checkmarx/ast/wrapper/Execution.java b/src/main/java/com/checkmarx/ast/wrapper/Execution.java
index c233ff2..0a888ec 100644
--- a/src/main/java/com/checkmarx/ast/wrapper/Execution.java
+++ b/src/main/java/com/checkmarx/ast/wrapper/Execution.java
@@ -1,5 +1,6 @@
package com.checkmarx.ast.wrapper;
+import com.checkmarx.ast.kicsRealtimeResults.KicsRealtimeResults;
import lombok.NonNull;
import org.slf4j.Logger;
@@ -12,10 +13,7 @@
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
+import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -171,7 +169,7 @@ private static String detectBinaryName(@NonNull Logger logger) {
return fileName;
}
- private static String getOperatingSystemType(String osName) {
+ public static String getOperatingSystemType(String osName) {
if (osName.contains(OS_LINUX)) {
return OS_LINUX;
} else if (osName.contains(OS_WINDOWS)) {
@@ -217,4 +215,6 @@ private static String md5(InputStream a) {
}
return md5;
}
+
+
}
diff --git a/src/main/resources/cx-linux b/src/main/resources/cx-linux
index f9909fd..45b5e94 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:2972512d55630f04f494fdf3543a3edf37906b970f5e29f58127ad27c40b652c
-size 81023160
+oid sha256:0d4fb757538955c9f03b442698f5d0eb8bf2c1e2929c80a390ee8b4d8b50b1a7
+size 81084600
diff --git a/src/main/resources/cx-linux-arm b/src/main/resources/cx-linux-arm
index f17f02d..5caaf77 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:ba3e45134be18e2093521df1f5e337c749e231fc926d5928815be4bbc68c4dd0
-size 77332664
+oid sha256:21e6f82b4ff43975385902eec3f37bd724316a96cef4354b7a397667698f4fa3
+size 77398200
diff --git a/src/main/resources/cx-mac b/src/main/resources/cx-mac
index 52f3f40..0201cf8 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:7da05ed86e02142c414ebfa2ed335b71357f7d407291b9952bf5860c7bf78dab
-size 162975392
+oid sha256:1f2c1ddf46dbdd2df9c8ecb4ef89e737ea5037379cdb18862bbdbbe365eed1f8
+size 163091056
diff --git a/src/main/resources/cx.exe b/src/main/resources/cx.exe
index 0341df0..10b897b 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:5fb41bc7c8d5b3db18f4626d9523fc626fd19c6f88e1bb078184e7b6e2532fed
-size 82995136
+oid sha256:d1bed011ccbb22466be3b6747f3fb2e4005be197718e8b9d9489c686645fecbc
+size 83044800
diff --git a/src/test/java/com/checkmarx/ast/ScanTest.java b/src/test/java/com/checkmarx/ast/ScanTest.java
index 5e31b33..a414281 100644
--- a/src/test/java/com/checkmarx/ast/ScanTest.java
+++ b/src/test/java/com/checkmarx/ast/ScanTest.java
@@ -25,7 +25,7 @@ void testScanShow() throws Exception {
@Test
void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulResponseWithCorrectValues() throws Exception {
- ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode");
+ ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "vscode", null);
// Assertions for the scan result
Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null");
@@ -46,7 +46,7 @@ void testScanAsca_WhenFileWithVulnerabilitiesIsSentWithAgent_ReturnSuccessfulRes
@Test
void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseWithCorrectValues() throws Exception {
- ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null);
+ ScanResult scanResult = wrapper.ScanAsca("src/test/resources/csharp-no-vul.cs", true, null, null);
Assertions.assertNotNull(scanResult.getRequestId());
Assertions.assertTrue(scanResult.isStatus());
Assertions.assertNull(scanResult.getError());
@@ -55,12 +55,25 @@ void testScanAsca_WhenFileWithoutVulnerabilitiesIsSent_ReturnSuccessfulResponseW
@Test
void testScanAsca_WhenMissingFileExtension_ReturnFileExtensionIsRequiredFailure() throws Exception {
- ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null);
+ ScanResult scanResult = wrapper.ScanAsca("CODEOWNERS", true, null, null);
Assertions.assertNotNull(scanResult.getRequestId());
Assertions.assertNotNull(scanResult.getError());
Assertions.assertEquals("The file name must have an extension.", scanResult.getError().getDescription());
}
+ @Test
+ void testScanAsca_WithIgnoreFilePath_ShouldWorkCorrectly() throws Exception {
+ String ignoreFile = "src/test/resources/ignored-packages.json";
+
+ // Test with ignore file - should not break the scanning process
+ ScanResult scanResult = wrapper.ScanAsca("src/test/resources/python-vul-file.py", true, "test-agent", ignoreFile);
+
+ // Verify the scan completes successfully
+ Assertions.assertNotNull(scanResult.getRequestId(), "Request ID should not be null");
+ Assertions.assertTrue(scanResult.isStatus(), "Status should be true");
+ Assertions.assertNull(scanResult.getError(), "Error should be null when scan is successful");
+ }
+
@Test
void testScanList() throws Exception {
List cxOutput = wrapper.scanList("limit=10");
diff --git a/src/test/java/com/checkmarx/ast/TenantTest.java b/src/test/java/com/checkmarx/ast/TenantTest.java
index 7f49da1..91824f4 100644
--- a/src/test/java/com/checkmarx/ast/TenantTest.java
+++ b/src/test/java/com/checkmarx/ast/TenantTest.java
@@ -24,4 +24,14 @@ void testAiMcpServerEnabled() throws Exception {
boolean enabled = Assertions.assertDoesNotThrow(() -> wrapper.aiMcpServerEnabled());
Assertions.assertTrue(enabled, "AI MCP Server flag expected to be true");
}
+
+ @Test
+ void testDevAssistEnabled() {
+ Assertions.assertDoesNotThrow(() -> wrapper.devAssistEnabled());
+ }
+
+ @Test
+ void testOneAssistEnabled() {
+ Assertions.assertDoesNotThrow(() -> wrapper.oneAssistEnabled());
+ }
}