customStates = wrapperProvider.triageGetStates(false);
allStates = customStates.stream().map(CustomState::getName).collect(Collectors.toList());
} catch (Exception e) {
CxLogger.warning("Could not fetch platform states: " + e.getMessage());
diff --git a/common-lib/.classpath b/common-lib/.classpath
index 31c2a6e0..83424dc7 100644
--- a/common-lib/.classpath
+++ b/common-lib/.classpath
@@ -7,7 +7,7 @@
-
+
diff --git a/common-lib/META-INF/MANIFEST.MF b/common-lib/META-INF/MANIFEST.MF
index 97bd5d8d..0bb9fd72 100644
--- a/common-lib/META-INF/MANIFEST.MF
+++ b/common-lib/META-INF/MANIFEST.MF
@@ -5,7 +5,7 @@ Bundle-SymbolicName: com.checkmarx.eclipse.common
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: Checkmarx
Bundle-ClassPath: .,
- lib/ast-cli-java-wrapper-2.4.24.jar,
+ lib/ast-cli-java-wrapper-2.4.27.jar,
lib/jackson-core-2.21.4.jar,
lib/jackson-databind-2.21.5.jar,
lib/jackson-annotations-2.21.jar,
@@ -22,40 +22,41 @@ Require-Bundle: org.eclipse.core.runtime,
org.eclipse.ui.workbench.texteditor,
org.eclipse.ui.editors
Bundle-RequiredExecutionEnvironment: JavaSE-17
-Export-Package: com.checkmarx.eclipse.common.enums,
- com.checkmarx.eclipse.common.events,
- com.checkmarx.eclipse.common.listener,
- com.checkmarx.eclipse.common.preferences,
- com.checkmarx.eclipse.common.runner,
- com.checkmarx.eclipse.common.utils,
- org.apache.commons.lang3,
- org.apache.commons.lang3.builder,
- org.apache.commons.lang3.exception,
- org.apache.commons.lang3.text,
- org.apache.commons.lang3.time,
- org.apache.commons.lang3.tuple,
- com.checkmarx.ast.wrapper,
- com.checkmarx.ast.project,
- com.checkmarx.ast.scan,
- com.checkmarx.ast.results,
- com.checkmarx.ast.results.result,
+Export-Package: com.checkmarx.ast.asca,
com.checkmarx.ast.codebashing,
- com.checkmarx.ast.learnMore,
- com.checkmarx.ast.predicate,
- com.checkmarx.ast.asca,
com.checkmarx.ast.containersrealtime,
com.checkmarx.ast.iacrealtime,
com.checkmarx.ast.kicsRealtimeResults,
com.checkmarx.ast.kicsRealtimeResults.ast.kicsRealtimeResult,
+ com.checkmarx.ast.learnMore,
com.checkmarx.ast.mask,
com.checkmarx.ast.ossrealtime,
+ com.checkmarx.ast.predicate,
+ com.checkmarx.ast.project,
com.checkmarx.ast.realtime,
com.checkmarx.ast.remediation,
+ com.checkmarx.ast.results,
+ com.checkmarx.ast.results.result,
+ com.checkmarx.ast.scan,
com.checkmarx.ast.secretsrealtime,
com.checkmarx.ast.tenant,
com.checkmarx.ast.utils,
+ com.checkmarx.ast.wrapper,
+ com.checkmarx.eclipse.common.enums,
+ com.checkmarx.eclipse.common.events,
+ com.checkmarx.eclipse.common.listener,
+ com.checkmarx.eclipse.common.preferences,
+ com.checkmarx.eclipse.common.runner,
+ com.checkmarx.eclipse.common.utils,
+ com.checkmarx.eclipse.common.wrapper,
com.fasterxml.jackson.annotation,
com.fasterxml.jackson.core,
com.fasterxml.jackson.core.type,
com.fasterxml.jackson.databind,
+ org.apache.commons.lang3,
+ org.apache.commons.lang3.builder,
+ org.apache.commons.lang3.exception,
+ org.apache.commons.lang3.text,
+ org.apache.commons.lang3.time,
+ org.apache.commons.lang3.tuple,
org.slf4j
diff --git a/common-lib/build.properties b/common-lib/build.properties
index 78e4ef65..a1599af2 100644
--- a/common-lib/build.properties
+++ b/common-lib/build.properties
@@ -1,6 +1,6 @@
output.. = bin/
bin.includes = META-INF/,\
- lib/ast-cli-java-wrapper-2.4.24.jar,\
+ lib/ast-cli-java-wrapper-2.4.27.jar,\
lib/jackson-core-2.21.4.jar,\
lib/jackson-databind-2.21.5.jar,\
lib/jackson-annotations-2.21.jar,\
diff --git a/common-lib/lib/ast-cli-java-wrapper-2.4.24.jar b/common-lib/lib/ast-cli-java-wrapper-2.4.24.jar
deleted file mode 100644
index b4e1e934..00000000
--- a/common-lib/lib/ast-cli-java-wrapper-2.4.24.jar
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e664771fd767accd5bd47057c5a6d4cc86d292c93191200d061ded6e3e527bdf
-size 135732567
diff --git a/common-lib/lib/ast-cli-java-wrapper-2.4.27.jar b/common-lib/lib/ast-cli-java-wrapper-2.4.27.jar
new file mode 100644
index 00000000..3df6ef7a
--- /dev/null
+++ b/common-lib/lib/ast-cli-java-wrapper-2.4.27.jar
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5313792421835ddfd332713da41a82eb8234705c981bdbdd1431d797538185c0
+size 140401594
diff --git a/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpInstallCallback.java b/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpInstallCallback.java
new file mode 100644
index 00000000..26915cab
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpInstallCallback.java
@@ -0,0 +1,31 @@
+package com.checkmarx.eclipse.common.listener;
+
+/**
+ * Receives the outcome of an MCP install triggered from the UI (e.g. the "Install
+ * MCP" link on CheckmarxPreferencePage). Unlike the silent, best-effort auto-install
+ * run at startup, a user-initiated install needs to report back whether it actually
+ * succeeded so the UI can show a result message.
+ *
+ * May be invoked from a background thread - implementations that touch SWT
+ * widgets must marshal onto the display thread themselves.
+ */
+public interface IMcpInstallCallback {
+
+ /**
+ * Called when the MCP configuration was installed/updated successfully.
+ */
+ void onSuccess();
+
+ /**
+ * Called when the install ran successfully but there was nothing to change - the
+ * server entry already matches the current API key/URL exactly.
+ */
+ void onAlreadyUpToDate();
+
+ /**
+ * Called when the install could not be completed.
+ *
+ * @param errorMessage a user-presentable reason for the failure
+ */
+ void onFailure(String errorMessage);
+}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpInstallHandler.java b/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpInstallHandler.java
new file mode 100644
index 00000000..13f34554
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpInstallHandler.java
@@ -0,0 +1,19 @@
+package com.checkmarx.eclipse.common.listener;
+
+/**
+ * Service for installing the Checkmarx MCP server configuration.
+ *
+ * Allows preference pages in common-lib (e.g. CheckmarxPreferencePage) to trigger
+ * MCP installation without depending on devassist-lib, which owns the actual
+ * McpInstallService implementation.
+ */
+public interface IMcpInstallHandler {
+
+ /**
+ * Installs/updates the Checkmarx MCP server configuration for the currently
+ * authenticated user, reporting the outcome to the given callback.
+ *
+ * @param callback notified of success or failure, possibly from a background thread
+ */
+ void installMcp(IMcpInstallCallback callback);
+}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/preferences/CheckmarxPreferencePage.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/CheckmarxPreferencePage.java
index 92e00f29..25ef944b 100644
--- a/common-lib/src/com/checkmarx/eclipse/common/preferences/CheckmarxPreferencePage.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/CheckmarxPreferencePage.java
@@ -3,7 +3,6 @@
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
-import org.apache.commons.lang3.StringUtils;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.preference.PreferencePage;
@@ -19,6 +18,9 @@
import org.eclipse.ui.dialogs.PreferencesUtil;
import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.utils.PluginConstants;
+import com.checkmarx.eclipse.common.listener.IMcpInstallCallback;
+import com.checkmarx.eclipse.common.listener.IMcpInstallHandler;
import com.checkmarx.eclipse.common.listener.ISettingsChangeNotifier;
/**
@@ -43,21 +45,9 @@ public class CheckmarxPreferencePage extends PreferencePage implements IWorkbenc
private Button containersCheckbox;
private Button iacCheckbox;
private Combo containersToolCombo;
+ private Label mcpStatusLabel;
private boolean loggedIn;
- public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_OSS_TITLE = "Checkmarx Developer Assist Open Source Realtime Scanner (OSS-Realtime): Activate OSS-Realtime";
- public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_SECRETS_TITLE = "Checkmarx Developer Assist Secret Detection Realtime Scanner: Activate Secret Detection Realtime";
- public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_CONTAINERS_TITLE = "Checkmarx Developer Assist Containers Realtime Scanner: Activate Containers Realtime";
- public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_TITLE = "Checkmarx Developer Assist IAC Realtime Scanner: Activate IAC Realtime";
- public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_ASCA_TITLE = "Checkmarx Developer Assist AI Secure Coding Assistant (ASCA): Activate ASCA";
- public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_PREFIX = "Checkmarx Developer Assist IAC Realtime Scanner: Containers Management Tool";
- public static final String DEVASSIST_PLUGIN_WELCOME_TITLE = "Welcome to Checkmarx Developer Assist";
- public static final String CONTAINERS_TOOL_DESCRIPTION = "Select the Containers Management Tool to use for IaC scanning.";
- public static final String OSS_REALTIME_CHECKBOX = "Scans your manifest files as you code";
- public static final String SECRETS_REALTIME_CHECKBOX = "Scans your files for potential secrets and credentials as you code";
- public static final String CONTAINERS_REALTIME_CHECKBOX = "Scans your Docker files and container configurations as you code";
- public static final String IAC_REALTIME_CHECKBOX = "Scans your Infrastructure as Code files as you code";
- public static final String ASCA_CHECKBOX = "Scan your file as you code";
public CheckmarxPreferencePage() {
super();
@@ -80,7 +70,7 @@ public CheckmarxPreferencePage() {
private void handlePreferenceChange(PropertyChangeEvent event) {
// Re-check login state: if API key was cleared, we need to switch from
// logged-in scanner checkboxes to logged-out message
- boolean isNowLoggedIn = StringUtils.isNotBlank(Preferences.getApiKey());
+ boolean isNowLoggedIn = Preferences.isAuthenticated();
if (loggedIn != isNowLoggedIn) {
loggedIn = isNowLoggedIn;
}
@@ -88,7 +78,7 @@ private void handlePreferenceChange(PropertyChangeEvent event) {
@Override
protected Control createContents(Composite parent) {
- loggedIn = StringUtils.isNotBlank(Preferences.getApiKey());
+ loggedIn = Preferences.isAuthenticated();
if (!loggedIn) {
return createLoggedOutContent(parent);
}
@@ -109,51 +99,168 @@ protected Control createContents(Composite parent) {
assistMessageLabel.setVisible(false);
// --- ASCA Section ---
- createSectionHeader(mainPanel, DEVASSIST_PLUGIN_REALTIME_SCANNERS_ASCA_TITLE);
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_ASCA_TITLE);
Composite ascaComp = createIndentComposite(mainPanel);
ascaCheckbox = new Button(ascaComp, SWT.CHECK);
- ascaCheckbox.setText(ASCA_CHECKBOX);
+ ascaCheckbox.setText(PluginConstants.ASCA_CHECKBOX);
// --- OSS Section ---
- createSectionHeader(mainPanel, DEVASSIST_PLUGIN_REALTIME_SCANNERS_OSS_TITLE);
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_OSS_TITLE);
Composite ossComp = createIndentComposite(mainPanel);
ossCheckbox = new Button(ossComp, SWT.CHECK);
- ossCheckbox.setText(OSS_REALTIME_CHECKBOX);
+ ossCheckbox.setText(PluginConstants.OSS_REALTIME_CHECKBOX);
// --- Secrets Section ---
- createSectionHeader(mainPanel, DEVASSIST_PLUGIN_REALTIME_SCANNERS_SECRETS_TITLE);
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_SECRETS_TITLE);
Composite secretsComp = createIndentComposite(mainPanel);
secretsCheckbox = new Button(secretsComp, SWT.CHECK);
- secretsCheckbox.setText(SECRETS_REALTIME_CHECKBOX);
+ secretsCheckbox.setText(PluginConstants.SECRETS_REALTIME_CHECKBOX);
// --- Containers Section ---
- createSectionHeader(mainPanel, DEVASSIST_PLUGIN_REALTIME_SCANNERS_CONTAINERS_TITLE);
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_CONTAINERS_TITLE);
Composite containersComp = createIndentComposite(mainPanel);
containersCheckbox = new Button(containersComp, SWT.CHECK);
- containersCheckbox.setText(CONTAINERS_REALTIME_CHECKBOX);
+ containersCheckbox.setText(PluginConstants.CONTAINERS_REALTIME_CHECKBOX);
// --- IaC Section ---
- createSectionHeader(mainPanel, DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_TITLE);
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_TITLE);
Composite iacComp = createIndentComposite(mainPanel);
iacCheckbox = new Button(iacComp, SWT.CHECK);
- iacCheckbox.setText(IAC_REALTIME_CHECKBOX);
+ iacCheckbox.setText(PluginConstants.IAC_REALTIME_CHECKBOX);
// --- Container Tool Selection Section ---
- createSectionHeader(mainPanel, DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_PREFIX);
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_PREFIX);
Composite containerToolComp = createIndentComposite(mainPanel);
Label containerDesc = new Label(containerToolComp, SWT.WRAP);
- containerDesc.setText(CONTAINERS_TOOL_DESCRIPTION);
+ containerDesc.setText(PluginConstants.CONTAINERS_TOOL_DESCRIPTION);
GridData descData = new GridData(GridData.FILL_HORIZONTAL);
containerDesc.setLayoutData(descData);
containersToolCombo = new Combo(containerToolComp, SWT.READ_ONLY);
- containersToolCombo.setItems(new String[] { "docker", "podman" });
+ containersToolCombo.setItems(PluginConstants.CONTAINERS_TOOLS);
containersToolCombo.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
+ // --- Checkmarx MCP Section ---
+ // A horizontal rule marks this as a distinct settings group, separate from the
+ // Realtime Scanner sections above.
+ Label mcpSeparator = new Label(mainPanel, SWT.SEPARATOR | SWT.HORIZONTAL);
+ GridData mcpSeparatorData = new GridData(GridData.FILL_HORIZONTAL);
+ mcpSeparatorData.verticalIndent = 6;
+ mcpSeparator.setLayoutData(mcpSeparatorData);
+
+ createSectionHeader(mainPanel, PluginConstants.CHECKMARX_MCP_SECTION_TITLE);
+ Composite mcpComp = createIndentComposite(mainPanel);
+
+ Label mcpDesc = new Label(mcpComp, SWT.WRAP);
+ mcpDesc.setText(PluginConstants.MCP_DESCRIPTION);
+ mcpDesc.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ // installMcpLink and mcpStatusLabel share a row so the result message appears
+ // right next to the link that triggered it, rather than on its own line.
+ Composite installMcpRow = new Composite(mcpComp, SWT.NONE);
+ GridLayout installMcpRowLayout = new GridLayout(2, false);
+ installMcpRowLayout.marginWidth = 0;
+ installMcpRowLayout.marginHeight = 0;
+ installMcpRowLayout.horizontalSpacing = 10;
+ installMcpRow.setLayout(installMcpRowLayout);
+ installMcpRow.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+ Link installMcpLink = new Link(installMcpRow, SWT.NONE);
+ installMcpLink.setText("" + PluginConstants.INSTALL_MCP_LINK_TEXT + "");
+ installMcpLink.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
+
+ mcpStatusLabel = new Label(installMcpRow, SWT.WRAP);
+ mcpStatusLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+ installMcpLink.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ installMcp();
+ }
+ });
+
+ Link editMcpSettingsLink = new Link(mcpComp, SWT.NONE);
+ editMcpSettingsLink.setText("" + PluginConstants.EDIT_MCP_SETTINGS_LINK_TEXT + "");
+ editMcpSettingsLink.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
+ editMcpSettingsLink.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ editMcpSettings();
+ }
+ });
+
loadValues();
return mainPanel;
}
+ /**
+ * Installs/updates the Checkmarx MCP server configuration. Delegates to the handler
+ * registered by devassist-lib (this bundle - common-lib - doesn't depend on it directly),
+ * and shows the result right next to the "Install MCP" link.
+ */
+ private void installMcp() {
+ IMcpInstallHandler handler = Preferences.getMcpInstallHandler();
+ if (handler == null) {
+ CxLogger.warning("[PREFS] MCP install requested before the handler was registered");
+ showMcpStatus(false, PluginConstants.MCP_INSTALL_UNAVAILABLE_MESSAGE);
+ return;
+ }
+
+ showMcpStatus(null, PluginConstants.MCP_INSTALLING_STATE);
+
+ handler.installMcp(new IMcpInstallCallback() {
+ @Override
+ public void onSuccess() {
+ Display.getDefault().asyncExec(() -> showMcpStatus(true, PluginConstants.MCP_INSTALL_SUCCESS_MESSAGE));
+ }
+
+ @Override
+ public void onAlreadyUpToDate() {
+ Display.getDefault().asyncExec(() -> showMcpStatus(true, PluginConstants.MCP_ALREADY_UP_TO_DATE_MESSAGE));
+ }
+
+ @Override
+ public void onFailure(String errorMessage) {
+ Display.getDefault().asyncExec(() -> showMcpStatus(false, errorMessage));
+ }
+ });
+ }
+
+ /**
+ * Updates mcpStatusLabel with an install result/progress message.
+ *
+ * @param success true = success (green), false = failure (red), null = in-progress
+ * (default color)
+ */
+ private void showMcpStatus(Boolean success, String message) {
+ if (mcpStatusLabel == null || mcpStatusLabel.isDisposed()) {
+ return;
+ }
+
+ Display display = mcpStatusLabel.getDisplay();
+ if (success == null) {
+ mcpStatusLabel.setForeground(null);
+ } else if (success) {
+ mcpStatusLabel.setForeground(display.getSystemColor(SWT.COLOR_DARK_GREEN));
+ } else {
+ mcpStatusLabel.setForeground(display.getSystemColor(SWT.COLOR_RED));
+ }
+ mcpStatusLabel.setText(message);
+ mcpStatusLabel.getParent().layout(true, true);
+ }
+
+ /**
+ * Opens GitHub Copilot for Eclipse's own MCP preference page, where the Checkmarx MCP
+ * server entry (once installed) can be reviewed/edited alongside any other MCP servers.
+ */
+ private void editMcpSettings() {
+ PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(getShell(),
+ PluginConstants.COPILOT_MCP_PREFERENCE_PAGE_ID, null, null);
+ if (dialog != null) {
+ dialog.open();
+ }
+ }
+
/**
* Shown instead of the scanner checkboxes when the user isn't logged in - there
* is nothing meaningful to configure until credentials are set in "Checkmarx
@@ -167,11 +274,11 @@ private Control createLoggedOutContent(Composite parent) {
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
Label message = new Label(composite, SWT.WRAP);
- message.setText("Log in to Checkmarx One to configure Realtime Scanners.");
+ message.setText(PluginConstants.LOGIN_NOTE_CXONE_ASSIST);
message.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
Link goToLoginLink = new Link(composite, SWT.NONE);
- goToLoginLink.setText("Go to Checkmarx One preferences");
+ goToLoginLink.setText(""+PluginConstants.GO_TO_CHECKMARX_ONE+"");
goToLoginLink.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
goToLoginLink.addSelectionListener(new SelectionAdapter() {
@Override
@@ -217,7 +324,7 @@ private void loadValues() {
protected void performDefaults() {
// Check credentials fresh, not from captured field.
// If user logged out while viewing another page, loggedIn would be stale.
- boolean isCurrentlyLoggedIn = StringUtils.isNotBlank(Preferences.getApiKey());
+ boolean isCurrentlyLoggedIn = Preferences.isAuthenticated();
if (!isCurrentlyLoggedIn) {
super.performDefaults();
return;
@@ -276,7 +383,7 @@ public boolean performOk() {
// dialog session,
// loggedIn would be stale and we'd save/notify with false authentication
// status.
- boolean isCurrentlyLoggedIn = StringUtils.isNotBlank(Preferences.getApiKey());
+ boolean isCurrentlyLoggedIn = Preferences.isAuthenticated();
if (!isCurrentlyLoggedIn) {
return super.performOk();
}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/preferences/Preferences.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/Preferences.java
index b6c6b3e5..44fe2628 100644
--- a/common-lib/src/com/checkmarx/eclipse/common/preferences/Preferences.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/Preferences.java
@@ -11,6 +11,7 @@
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import com.checkmarx.eclipse.common.listener.IAuthenticationSuccessHandler;
+import com.checkmarx.eclipse.common.listener.IMcpInstallHandler;
import com.checkmarx.eclipse.common.listener.ISettingsChangeNotifier;
import com.checkmarx.eclipse.common.listener.IWorkspaceScanService;
@@ -54,6 +55,9 @@ public class Preferences {
// Service for triggering workspace scans (registered by main plugin)
private static IWorkspaceScanService workspaceScanService;
+ // Handler for installing the Checkmarx MCP server configuration (registered by devassist-lib)
+ private static IMcpInstallHandler mcpInstallHandler;
+
private Preferences() {
}
@@ -74,11 +78,6 @@ public static void store(String key, String value) {
STORE.setValue(key, value);
}
- public static void clearApiKey() {
- STORE.setValue(API_KEY, "");
- STORE.setValue(CREDENTIALS_VALIDATED, false);
- }
-
public static boolean isCredentialsValidated() {
return STORE.getBoolean(CREDENTIALS_VALIDATED);
}
@@ -87,6 +86,16 @@ public static void setCredentialsValidated(boolean validated) {
STORE.setValue(CREDENTIALS_VALIDATED, validated);
}
+ /**
+ * Single source of truth for "is the user logged in", independent of which credential
+ * type produced that state. Callers across the plugin should check this - not API key
+ * presence - so that a future auth method (e.g. OAuth) only needs to set/clear this same
+ * flag to plug into every existing authenticated-only code path.
+ */
+ public static boolean isAuthenticated() {
+ return isCredentialsValidated();
+ }
+
public static void setAuthenticationSuccessHandler(IAuthenticationSuccessHandler handler) {
authSuccessHandler = handler;
}
@@ -111,6 +120,14 @@ public static IWorkspaceScanService getWorkspaceScanService() {
return workspaceScanService;
}
+ public static void setMcpInstallHandler(IMcpInstallHandler handler) {
+ mcpInstallHandler = handler;
+ }
+
+ public static IMcpInstallHandler getMcpInstallHandler() {
+ return mcpInstallHandler;
+ }
+
// ============================================================================
// USER PREFERENCES - Preserve user's scanner choices across feature toggles
// Mirrors JetBrains GlobalSettingsState.setUserPreferences() pattern
diff --git a/common-lib/src/com/checkmarx/eclipse/common/preferences/PreferencesPage.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/PreferencesPage.java
index a56832a5..6402c468 100644
--- a/common-lib/src/com/checkmarx/eclipse/common/preferences/PreferencesPage.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/PreferencesPage.java
@@ -5,7 +5,7 @@
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
-import org.eclipse.jface.preference.FieldEditor;
+import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.preference.StringFieldEditor;
@@ -35,13 +35,16 @@
import com.checkmarx.eclipse.common.runner.TenantSettingsProvider;
import com.checkmarx.eclipse.common.utils.CxLogger;
+/**
+ * PreferencesPage class for Chekmarx One Preference Page (Login settings)
+ */
public class PreferencesPage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
// Captured once the fields are loaded, so performOk() can tell whether THIS
// page's own settings actually changed. Needed because Eclipse's shared
// Preferences dialog calls performOk() on every page the user visited during
// the session - not just the one they edited - so simply opening/looking at
- // "Checkmarx One" while really only changing "Checkmarx Scanner Configuration"
+ // "Checkmarx One" while really only changing "Checkmarx One Assist"
// (Realtime Scanners) would otherwise still unconditionally fire
// TOPIC_APPLY_SETTINGS below and refresh the unrelated Checkmarx One scan view.
private StringFieldEditor apiKeyField;
@@ -50,6 +53,18 @@ public class PreferencesPage extends FieldEditorPreferencePage implements IWorkb
private String initialAdditionalOptions;
private Link realtimeScannersLink;
+ // The API key that was actually confirmed against the server (set on successful
+ // login,
+ // cleared on logout). Comparing the current field value against this - rather
+ // than
+ // clearing Preferences.CREDENTIALS_VALIDATED from a text ModifyListener -
+ // avoids reacting
+ // to StringFieldEditor.load() re-populating the field from the store on every
+ // page open,
+ // which would otherwise wipe the "connected" flag before the user ever touched
+ // anything.
+ private String lastValidatedApiKey;
+
public PreferencesPage() {
super(GRID);
// Replaced Activator preference store listener with Preferences.STORE
@@ -66,10 +81,10 @@ private void handlePropertyChange(PropertyChangeEvent event) {
*/
private void refreshRealtimeScannersLink() {
if (realtimeScannersLink != null && !realtimeScannersLink.isDisposed()) {
- boolean isLoggedIn = StringUtils.isNotBlank(Preferences.getApiKey());
-
+ boolean isLoggedIn = Preferences.isAuthenticated();
+
realtimeScannersLink.setVisible(isLoggedIn);
-
+
if (realtimeScannersLink.getLayoutData() instanceof GridData) {
((GridData) realtimeScannersLink.getLayoutData()).exclude = !isLoggedIn;
}
@@ -85,7 +100,7 @@ private void refreshRealtimeScannersLink() {
@Override
public void init(IWorkbench workbench) {
setPreferenceStore(Preferences.STORE);
- setMessage("Checkmarx One preferences");
+ setMessage(PluginConstants.CHECKMARX_ONE);
}
@Override
@@ -107,72 +122,194 @@ protected void createFieldEditors() {
parentLayout.marginWidth = 0;
topComposite.setLayout(parentLayout);
- StringFieldEditor apiKey = new StringFieldEditor(Preferences.API_KEY, PluginConstants.PREFERENCES_API_KEY, topComposite);
+ // Every widget on this page is parented directly to topComposite, in the exact
+ // order
+ // it should visually appear. They used to be split between topComposite and
+ // getFieldEditorParent(), which made the on-screen order depend on which of the
+ // two
+ // composites was created first rather than on the order of the code below -
+ // keeping a
+ // single parent removes that ambiguity.
+
+ // helpLink lives in its own composite, isolated from the fields below, so its
+ // own
+ // sizing/margins can never influence the spacing between the API key /
+ // additional
+ // params labels and their input boxes.
+ Composite helpComposite = new Composite(topComposite, SWT.NONE);
+ GridLayout helpLayout = new GridLayout();
+ helpLayout.numColumns = 1;
+ helpLayout.marginHeight = 0;
+ helpLayout.marginWidth = 0;
+ helpComposite.setLayout(helpLayout);
+ helpComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+ Link helpLink = new Link(helpComposite, SWT.NONE);
+ helpLink.setText(""
+ + PluginConstants.PREFERENCES_HELP_LINK_TEXT + "");
+ helpLink.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
+ helpLink.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ IWorkbenchBrowserSupport browserSupport = PlatformUI.getWorkbench().getBrowserSupport();
+ try {
+ browserSupport.getExternalBrowser().openURL(new URL(e.text));
+ } catch (PartInitException | MalformedURLException e1) {
+ CxLogger.error("Failed to open Checkmarx One Eclipse Plugin Help Page link.", e1);
+ e1.printStackTrace();
+ }
+ }
+ });
+
+ spacer(topComposite);
+
+ // apiKey and additionalParams get their own composite with a standard, fixed
+ // label-to-input gap - kept separate from topComposite (and from helpComposite
+ // above)
+ // so nothing else on the page can stretch or shrink that gap.
+ Composite fieldsComposite = new Composite(topComposite, SWT.NONE);
+ GridLayout fieldsLayout = new GridLayout();
+ // Use 2 columns so each FieldEditor places its label in column 1 and the
+ // input control in column 2. This allows us to set a widthHint on the
+ // input control without the control stretching to the full dialog width.
+ fieldsLayout.numColumns = 2;
+ fieldsLayout.marginHeight = 0;
+ fieldsLayout.marginWidth = 0;
+ fieldsLayout.verticalSpacing = 4;
+ fieldsComposite.setLayout(fieldsLayout);
+ fieldsComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+ StringFieldEditor apiKey = new StringFieldEditor(Preferences.API_KEY, PluginConstants.PREFERENCES_API_KEY,
+ fieldsComposite);
apiKeyField = apiKey;
addField(apiKey);
- Text textControl = apiKey.getTextControl(topComposite);
+ Text textControl = apiKey.getTextControl(fieldsComposite);
textControl.setEchoChar('*');
+ // Set fixed width for apiKey field
+ GridData apiKeyGridData = new GridData(SWT.BEGINNING, SWT.CENTER, true, false);
+ apiKeyGridData.widthHint = 500;
+ apiKeyGridData.grabExcessHorizontalSpace = false;
+ apiKeyGridData.horizontalAlignment = GridData.FILL;
+ if (textControl != null && !textControl.isDisposed()) {
+ textControl.setLayoutData(apiKeyGridData);
+ }
StringFieldEditor additionalParams = new StringFieldEditor(Preferences.ADDITIONAL_OPTIONS,
- PluginConstants.PREFERENCES_ADDITIONAL_OPTIONS, StringFieldEditor.UNLIMITED, StringFieldEditor.VALIDATE_ON_KEY_STROKE, topComposite);
+ PluginConstants.PREFERENCES_ADDITIONAL_OPTIONS, StringFieldEditor.UNLIMITED,
+ StringFieldEditor.VALIDATE_ON_KEY_STROKE, fieldsComposite);
additionalParamsField = additionalParams;
addField(additionalParams);
+ // Ensure the Additional Params text control has a fixed width similar to the API key
+ Text additionalTextControl = additionalParams.getTextControl(fieldsComposite);
+ GridData additionalGridData = new GridData(SWT.BEGINNING, SWT.CENTER, true, false);
+ additionalGridData.widthHint = 500; // match apiKey width
+ additionalGridData.grabExcessHorizontalSpace = false;
+ additionalGridData.horizontalAlignment = GridData.FILL;
+ if (additionalTextControl != null && !additionalTextControl.isDisposed()) {
+ additionalTextControl.setLayoutData(additionalGridData);
+ }
+
// Baseline for the change-detection guard in performOk() - captured now that
// both fields have loaded their values from the preference store.
initialApiKey = apiKey.getStringValue();
initialAdditionalOptions = additionalParams.getStringValue();
- //set the width for API Key text field
+ // Restore the "connected" state on reopening the page. Read directly from the
+ // preference store (not apiKey.getStringValue()) - the field editors haven't
+ // had
+ // load() called on them yet at this point in createFieldEditors(), so their
+ // text
+ // controls are still empty.
+ lastValidatedApiKey = (Preferences.isAuthenticated() && StringUtils.isNotBlank(Preferences.getApiKey()))
+ ? Preferences.getApiKey()
+ : null;
+ boolean isConnected = lastValidatedApiKey != null;
+ // Locked while connected, so the validated key can't be edited out from under the
+ // "connected" state - re-enabled on logout.
+ textControl.setEnabled(!isConnected);
+
+ // set the width for API Key text field
GridData gridData = new GridData(SWT.BEGINNING, SWT.CENTER, true, false);
gridData.widthHint = 500; // Some width
gridData.grabExcessHorizontalSpace = false;
gridData.horizontalAlignment = GridData.FILL;
textControl.setLayoutData(gridData);
- addField(space());
-
-
- Link cliHelp = new Link(getFieldEditorParent(), SWT.NONE);
- cliHelp.setText("CLI command that supports a set of global flags");
- cliHelp.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
- GridData linkGridData = new GridData(SWT.END, SWT.CENTER, true, false);
- cliHelp.setLayoutData(linkGridData);
+ Link cliHelp = new Link(topComposite, SWT.NONE);
+ cliHelp.setText(""
+ + PluginConstants.PREFERENCES_CLI_HELP_LINK_TEXT + "");
+ cliHelp.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
cliHelp.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- IWorkbenchBrowserSupport browserSupport = PlatformUI.getWorkbench().getBrowserSupport();
- try {
- browserSupport.getExternalBrowser().openURL(new URL(e.text));
- } catch (PartInitException | MalformedURLException e1) {
- CxLogger.error("Failed to open CLI help documentation link.", e1);
- e1.printStackTrace();
- }
- }
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ IWorkbenchBrowserSupport browserSupport = PlatformUI.getWorkbench().getBrowserSupport();
+ try {
+ browserSupport.getExternalBrowser().openURL(new URL(e.text));
+ } catch (PartInitException | MalformedURLException e1) {
+ CxLogger.error("Failed to open CLI help documentation link.", e1);
+ e1.printStackTrace();
+ }
+ }
});
- addField(space());
-
- Label connectionLabel = new Label(getFieldEditorParent(), SWT.WRAP);
- connectionLabel.setLayoutData(
- new GridData(SWT.FILL, SWT.CENTER, true, false)
- );
+ spacer(topComposite);
// Holds the Logout button reference so the Connect handler (defined before the
- // Logout button is created below) can disable/enable it during the connect flow.
+ // Logout button is created below) can disable/enable it during the connect
+ // flow.
final Button[] logoutButtonHolder = new Button[1];
- Button connectionButton = new Button(topComposite, SWT.PUSH);
- connectionButton.setText(PluginConstants.PREFERENCES_TEST_CONNECTION);
- connectionButton.setEnabled(!apiKey.getStringValue().trim().isEmpty());
- textControl.addModifyListener(e -> {
- connectionButton.setEnabled(!textControl.getText().trim().isEmpty());
+ Composite buttonsComposite = new Composite(topComposite, SWT.NONE);
+ GridLayout buttonsLayout = new GridLayout();
+ buttonsLayout.numColumns = 2;
+ buttonsLayout.marginHeight = 0;
+ buttonsLayout.marginWidth = 0;
+ buttonsLayout.horizontalSpacing = 10;
+ buttonsComposite.setLayout(buttonsLayout);
+ buttonsComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
+
+ // Give both buttons a fixed minimum width so they aren't sized to hug their
+ // text -
+ // without this, "Logout" ends up noticeably narrower than "Connect to
+ // Checkmarx".
+ final int buttonWidthHint = 140;
+
+ Button connectionButton = new Button(buttonsComposite, SWT.PUSH);
+ connectionButton.setText(PluginConstants.CONNECT_TO_CHECKMARX);
+ GridData connectionButtonGridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
+ connectionButtonGridData.widthHint = buttonWidthHint;
+ connectionButton.setLayoutData(connectionButtonGridData);
+ // Disabled while already connected - re-enabled on logout (see logoutButton
+ // below).
+ connectionButton.setEnabled(!isConnected);
+
+ // connectionLabel (the "Validating.../Connected" status text) is created after
+ // buttonsComposite so it renders below the Connect/Logout buttons, per
+ // AUTH_SUCCESS_DISPLAY
+ // placement - it's declared here, before the listeners below that reference it.
+ spacer(topComposite);
+
+ Label connectionLabel = new Label(topComposite, SWT.WRAP);
+ connectionLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+ if (isConnected) {
+ connectionLabel.setText(PluginConstants.AUTH_SUCCESS_DISPLAY);
+ setStatusLabelColor(connectionLabel, true);
+ }
- // Any edit means whatever gets saved next (even via Apply/OK without ever
- // clicking Test Connection) hasn't been checked against the server, so it must
- // not keep looking "connected" on the strength of a previous, different key's
- // validation.
- Preferences.setCredentialsValidated(false);
+ textControl.addModifyListener(e -> {
+ // This also fires when StringFieldEditor.load() programmatically repopulates
+ // the
+ // text field from the store on every page open - that's not a user edit.
+ // Comparing
+ // against lastValidatedApiKey (a value fixed for this page's lifetime, not
+ // re-read
+ // from the store) rather than clearing Preferences.CREDENTIALS_VALIDATED here
+ // keeps
+ // that load() from ever wiping the persisted "connected" flag.
+ boolean stillMatchesValidatedKey = lastValidatedApiKey != null
+ && textControl.getText().equals(lastValidatedApiKey);
+ connectionButton.setEnabled(!stillMatchesValidatedKey);
});
connectionButton.addSelectionListener(new SelectionAdapter() {
@@ -183,10 +320,12 @@ public void widgetSelected(SelectionEvent e) {
String additionalParams_str = additionalParams.getStringValue();
connectionButton.setEnabled(false);
connectionLabel.setText(PluginConstants.PREFERENCES_VALIDATING_STATE);
+ setStatusLabelColor(connectionLabel, null);
getFieldEditorParent().layout();
// Disable Logout for the duration of the connect/validate flow so a user can't
- // interrupt it mid-flight (e.g. closing the dialog or logging out) in a way that
+ // interrupt it mid-flight (e.g. closing the dialog or logging out) in a way
+ // that
// leaves the flow half-finished and the welcome dialog never shown.
if (logoutButtonHolder[0] != null && !logoutButtonHolder[0].isDisposed()) {
logoutButtonHolder[0].setEnabled(false);
@@ -194,8 +333,7 @@ public void widgetSelected(SelectionEvent e) {
CompletableFuture.supplyAsync(() -> {
try {
- return Authenticator.INSTANCE.doAuthentication(
- apiKey_str, additionalParams_str);
+ return Authenticator.INSTANCE.doAuthentication(apiKey_str, additionalParams_str);
} catch (Throwable t) {
CxLogger.error(PluginConstants.ERROR_AUTHENTICATING_AST, new Exception(t));
return t.getMessage();
@@ -205,9 +343,6 @@ public void widgetSelected(SelectionEvent e) {
// while this connect/validate call was in flight, these are disposed.
// Previously an unguarded call here threw and aborted this whole runnable,
// which is why the welcome dialog never appeared after closing the dialog.
- if (!connectionButton.isDisposed()) {
- connectionButton.setEnabled(true);
- }
// Show welcome dialog on successful authentication. The "Validating..."
// message is left on screen (not switched to "Connected") until the
@@ -222,9 +357,16 @@ public void widgetSelected(SelectionEvent e) {
Preferences.STORE.setValue(Preferences.API_KEY, apiKey_str);
Preferences.STORE.setValue(Preferences.ADDITIONAL_OPTIONS, additionalParams_str);
Preferences.setCredentialsValidated(true);
+ lastValidatedApiKey = apiKey_str;
+ // connectionButton stays disabled - it's only re-enabled on logout, or
+ // below if this attempt actually failed.
+ if (!textControl.isDisposed()) {
+ textControl.setEnabled(false);
+ }
refreshRealtimeScannersLink();
- // Notify views (CheckmarxView/CxFindingsView) that credentials are now available
+ // Notify views (CheckmarxView/CxFindingsView) that credentials are now
+ // available
// so they can switch from the credentials panel to the actual work views
for (ISettingsChangeNotifier notifier : Preferences.getSettingsChangeNotifiers()) {
notifier.notifySettingsApplied();
@@ -233,8 +375,8 @@ public void widgetSelected(SelectionEvent e) {
// Fetch MCP enabled status from server asynchronously
CompletableFuture.supplyAsync(() -> {
try {
- return TenantSettingsProvider.INSTANCE.isAiMcpServerEnabled(
- apiKey_str, additionalParams_str);
+ return TenantSettingsProvider.INSTANCE.isAiMcpServerEnabled(apiKey_str,
+ additionalParams_str);
} catch (Exception ex) {
CxLogger.error("Failed to fetch MCP status", ex);
return false;
@@ -242,6 +384,7 @@ public void widgetSelected(SelectionEvent e) {
}).thenAccept((mcpEnabled) -> Display.getDefault().syncExec(() -> {
if (!connectionLabel.isDisposed()) {
connectionLabel.setText(mapAuthResult(result));
+ setStatusLabelColor(connectionLabel, true);
}
if (!getFieldEditorParent().isDisposed()) {
getFieldEditorParent().layout();
@@ -249,9 +392,11 @@ public void widgetSelected(SelectionEvent e) {
// Delegate to handler registered by devassist-lib (if available)
IAuthenticationSuccessHandler handler = Preferences.getAuthenticationSuccessHandler();
if (handler != null) {
- handler.onAuthenticationSuccess(mcpEnabled, logoutButtonHolder[0], apiKey_str, additionalParams_str);
+ handler.onAuthenticationSuccess(mcpEnabled, logoutButtonHolder[0], apiKey_str,
+ additionalParams_str);
} else {
- CxLogger.warning("[PREFS] No authentication success handler registered - welcome dialog skipped");
+ CxLogger.warning(
+ "[PREFS] No authentication success handler registered - welcome dialog skipped");
if (logoutButtonHolder[0] != null && !logoutButtonHolder[0].isDisposed()) {
logoutButtonHolder[0].setEnabled(true);
}
@@ -259,9 +404,14 @@ public void widgetSelected(SelectionEvent e) {
}));
} else {
// Authentication failed - the flow ends here with no welcome dialog,
- // so show the failure message right away and restore Logout.
+ // so show the failure message right away, restore Logout, and let the
+ // user retry the connect.
+ if (!connectionButton.isDisposed()) {
+ connectionButton.setEnabled(true);
+ }
if (!connectionLabel.isDisposed()) {
connectionLabel.setText(mapAuthResult(result));
+ setStatusLabelColor(connectionLabel, false);
}
if (!getFieldEditorParent().isDisposed()) {
getFieldEditorParent().layout();
@@ -274,18 +424,36 @@ public void widgetSelected(SelectionEvent e) {
}
});
- addField(space());
-
- Button logoutButton = new Button(topComposite, SWT.PUSH);
+ Button logoutButton = new Button(buttonsComposite, SWT.PUSH);
logoutButtonHolder[0] = logoutButton;
- logoutButton.setText("Logout");
+ logoutButton.setText(PluginConstants.LOGOUT);
+ GridData logoutButtonGridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
+ logoutButtonGridData.widthHint = 80;
+ logoutButton.setLayoutData(logoutButtonGridData);
+ // Nothing to log out of until connected - mirrors connectionButton's inverse state.
+ logoutButton.setEnabled(isConnected);
logoutButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- Preferences.clearApiKey();
- apiKey.setStringValue("");
-// textControl.setText("");
-// connectionLabel.setText("");
+ MessageDialog confirmDialog = new MessageDialog(getShell(), PluginConstants.LOGOUT_CONFIRM_TITLE, null,
+ PluginConstants.LOGOUT_CONFIRM_MESSAGE, MessageDialog.QUESTION,
+ new String[] { "Yes", "Cancel" }, 0);
+ if (confirmDialog.open() != 0) {
+ return;
+ }
+
+ // Only mark the credentials as no longer validated - the API key itself stays
+ // stored and visible in the field. Every "am I logged in" check in the plugin
+ // now goes through Preferences.isAuthenticated() (not "API key non-blank"), so
+ // leaving the key in place here no longer makes any of them think the user is
+ // still logged in.
+ Preferences.setCredentialsValidated(false);
+ lastValidatedApiKey = null;
+ connectionButton.setEnabled(true);
+ textControl.setEnabled(true);
+ logoutButton.setEnabled(false);
+ connectionLabel.setText(PluginConstants.LOGOUT_SUCCESS_MESSAGE);
+ setStatusLabelColor(connectionLabel, true);
refreshRealtimeScannersLink();
getFieldEditorParent().layout();
@@ -300,10 +468,17 @@ public void widgetSelected(SelectionEvent e) {
}
});
- addField(space());
+ spacer(topComposite);
- realtimeScannersLink = new Link(getFieldEditorParent(), SWT.NONE);
- realtimeScannersLink.setText("Go to Realtime Scanners");
+ Label mcpSeparator = new Label(topComposite, SWT.SEPARATOR | SWT.HORIZONTAL);
+ GridData mcpSeparatorData = new GridData(GridData.FILL_HORIZONTAL);
+ mcpSeparatorData.verticalIndent = 6;
+ mcpSeparator.setLayoutData(mcpSeparatorData);
+
+ spacer(topComposite);
+
+ realtimeScannersLink = new Link(topComposite, SWT.NONE);
+ realtimeScannersLink.setText("" + PluginConstants.GO_TO_CHECKMARX_ONE_ASSIST + "");
realtimeScannersLink.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
// Call refresh after setting the LayoutData
@@ -312,29 +487,56 @@ public void widgetSelected(SelectionEvent e) {
realtimeScannersLink.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(
- getShell(),
- "com.checkmarx.eclipse.devassist.prefs.checkmarxpreferencepage",
- null,
- null
- );
+ PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(getShell(),
+ "com.checkmarx.eclipse.devassist.prefs.checkmarxpreferencepage", null, null);
if (dialog != null) {
CxPreferencesDialogSizing.applyTo(dialog);
dialog.open();
}
}
});
+
+ // Deferred via asyncExec - the dialog's shell isn't shown/realized yet at this point
+ // in createFieldEditors(), so an immediate setFocus() here would be ignored.
+ Display.getDefault().asyncExec(() -> {
+ if (!textControl.isDisposed()) {
+ textControl.setFocus();
+ }
+ });
}
private static String mapAuthResult(String result) {
if (result != null && result.contains(PluginConstants.AUTH_SUCCESS_PATTERN)) {
return PluginConstants.AUTH_SUCCESS_DISPLAY;
}
- return result;
+ // Log the actual failure reason (invalid key, network error, tenant misconfiguration,
+ // etc.) for diagnosis, but always show the user the same fixed message - the raw
+ // reason isn't reliably meaningful/actionable to them and may leak backend details.
+ CxLogger.error(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, result), new Exception(result));
+ return PluginConstants.AUTH_FAILURE_DISPLAY;
}
- private FieldEditor space() {
- return new LabelFieldEditor("", getFieldEditorParent());
+ private Label spacer(Composite parent) {
+ return new Label(parent, SWT.NONE);
+ }
+
+ /**
+ * Colors the login/logout status label: green for a success message (connected,
+ * logged out), red for a failure message, or the default color while a message is
+ * neutral (e.g. "Validating...").
+ */
+ private void setStatusLabelColor(Label label, Boolean success) {
+ if (label == null || label.isDisposed()) {
+ return;
+ }
+ Display display = label.getDisplay();
+ if (success == null) {
+ label.setForeground(null);
+ } else if (success) {
+ label.setForeground(display.getSystemColor(SWT.COLOR_DARK_GREEN));
+ } else {
+ label.setForeground(display.getSystemColor(SWT.COLOR_RED));
+ }
}
@Override
@@ -350,11 +552,20 @@ public boolean performOk() {
// this performOk() too when the user only meant to save realtime scanner
// settings, spuriously refreshing the Checkmarx One scan window.
String currentApiKey = apiKeyField != null ? apiKeyField.getStringValue() : null;
- String currentAdditionalOptions = additionalParamsField != null ? additionalParamsField.getStringValue() : null;
- boolean settingsActuallyChanged =
- !java.util.Objects.equals(currentApiKey, initialApiKey)
+ String currentAdditionalOptions = additionalParamsField != null ? additionalParamsField.getStringValue()
+ : null;
+ boolean settingsActuallyChanged = !java.util.Objects.equals(currentApiKey, initialApiKey)
|| !java.util.Objects.equals(currentAdditionalOptions, initialAdditionalOptions);
+ // The key being saved here is only "connected" if it's the exact one that was
+ // actually validated (via Connect) this session - if the user typed something
+ // different and hit OK/Apply without testing it, the persisted flag must not
+ // keep
+ // claiming it's validated.
+ if (!java.util.Objects.equals(currentApiKey, lastValidatedApiKey)) {
+ Preferences.setCredentialsValidated(false);
+ }
+
if (settingsActuallyChanged) {
// Notify main plugin that settings have changed
for (ISettingsChangeNotifier notifier : Preferences.getSettingsChangeNotifiers()) {
diff --git a/common-lib/src/com/checkmarx/eclipse/common/runner/Authenticator.java b/common-lib/src/com/checkmarx/eclipse/common/runner/Authenticator.java
index 61c497ce..2ed9b747 100644
--- a/common-lib/src/com/checkmarx/eclipse/common/runner/Authenticator.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/runner/Authenticator.java
@@ -1,19 +1,16 @@
package com.checkmarx.eclipse.common.runner;
-import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.checkmarx.ast.wrapper.CxConfig;
-import com.checkmarx.ast.wrapper.CxException;
-import com.checkmarx.ast.wrapper.CxWrapper;
import com.checkmarx.eclipse.common.utils.CxLogger;
import com.checkmarx.eclipse.common.utils.PluginConstants;
+import com.checkmarx.eclipse.common.wrapper.WrapperProvider;
public class Authenticator {
private final Logger log;
-
- private Authenticator() {
+
+ public Authenticator() {
this.log = LoggerFactory.getLogger(Authenticator.class);
}
@@ -26,16 +23,11 @@ public Authenticator(Logger logger) {
public static final Authenticator INSTANCE = new Authenticator();
public String doAuthentication(String apiKey, String additionalParams) {
- CxConfig config = CxConfig.builder()
- .apiKey(apiKey)
- .additionalParameters(additionalParams)
- .build();
try {
- CxWrapper wrapper = new CxWrapper(config, log);
- String cxValidateOutput = wrapper.authValidate();
+ String cxValidateOutput = new WrapperProvider().authValidate(apiKey, additionalParams);
CxLogger.info(String.format(PluginConstants.INFO_AUTHENTICATION_STATUS, cxValidateOutput));
return cxValidateOutput;
- } catch (IOException | InterruptedException | CxException e) {
+ } catch (Exception e) {
CxLogger.error(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, e.getMessage()), e);
return e.getMessage();
}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/runner/TenantSettingsProvider.java b/common-lib/src/com/checkmarx/eclipse/common/runner/TenantSettingsProvider.java
index f4ed02e8..2ed6a381 100644
--- a/common-lib/src/com/checkmarx/eclipse/common/runner/TenantSettingsProvider.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/runner/TenantSettingsProvider.java
@@ -1,21 +1,14 @@
package com.checkmarx.eclipse.common.runner;
-import java.io.IOException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.checkmarx.ast.wrapper.CxConfig;
-import com.checkmarx.ast.wrapper.CxException;
-import com.checkmarx.ast.wrapper.CxWrapper;
import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.wrapper.WrapperProvider;
/**
* Provides tenant-specific settings from the Checkmarx API.
* Fetches configuration details like MCP enablement status.
*/
public class TenantSettingsProvider {
- private static final Logger log = LoggerFactory.getLogger(TenantSettingsProvider.class);
+ private static final String LOG_PREFIX = "[TENANT_SETTINGS_PROVIDER] ";
public static final TenantSettingsProvider INSTANCE = new TenantSettingsProvider();
private TenantSettingsProvider() {
@@ -32,19 +25,12 @@ public boolean isAiMcpServerEnabled(String apiKey, String additionalParams) {
if (apiKey == null || apiKey.trim().isEmpty()) {
return false;
}
-
try {
- CxConfig config = CxConfig.builder()
- .apiKey(apiKey)
- .additionalParameters(additionalParams)
- .build();
-
- CxWrapper wrapper = new CxWrapper(config, log);
- boolean mcpEnabled = wrapper.aiMcpServerEnabled();
+ boolean mcpEnabled = new WrapperProvider().isAiMcpServerEnabled(apiKey, additionalParams);
CxLogger.info(String.format("MCP Server Status: %s", mcpEnabled ? "ENABLED" : "DISABLED"));
return mcpEnabled;
- } catch (IOException | InterruptedException | CxException e) {
- CxLogger.error("Failed to check MCP server status: " + e.getMessage(), e);
+ } catch (Exception e) {
+ CxLogger.error(String.format("%s Failed to check MCP server status: %s", LOG_PREFIX, e.getMessage()), e);
// Default to false on error to be conservative
return false;
}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/utils/PluginConstants.java b/common-lib/src/com/checkmarx/eclipse/common/utils/PluginConstants.java
index 76272e05..2e53df17 100644
--- a/common-lib/src/com/checkmarx/eclipse/common/utils/PluginConstants.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/utils/PluginConstants.java
@@ -3,6 +3,8 @@
import com.checkmarx.eclipse.common.events.SettingsTopics;
public class PluginConstants {
+ public static final String AGENT_NAME = "Eclipse";
+ public static final String CHECKMARX_ONE = "Checkmarx One";
public static final String EMPTY_STRING = "";
public static final String SAST = "sast";
public static final String SCA_DEPENDENCY = "sca";
@@ -53,7 +55,6 @@ public class PluginConstants {
public static final String INFO_CHANGE_BRANCH_EVENT_NOT_TRIGGERED = "Change branch event not triggered. Branch already selected";
public static final String INFO_CHANGE_PROJECT_EVENT_NOT_TRIGGERED = "Change project event not triggered. Project already selected";
public static final String AUTH_SUCCESS_PATTERN = "Successfully authenticated";
- public static final String AUTH_SUCCESS_DISPLAY = "You are connected to Checkmarx One";
/******************************** TREE MESSAGES ********************************/
public static final String TREE_INVALID_SCAN_ID_FORMAT = "Invalid scan id format.";
@@ -63,8 +64,21 @@ public class PluginConstants {
/******************************** PREFERENCES ********************************/
public static final String PREFERENCES_API_KEY = "API key:";
public static final String PREFERENCES_ADDITIONAL_OPTIONS = "Additional Params:";
- public static final String PREFERENCES_TEST_CONNECTION = "Test Connection";
+ public static final String CONNECT_TO_CHECKMARX = "Connect to Checkmarx";
+ public static final String LOGOUT = "Logout";
+ public static final String GO_TO_CHECKMARX_ONE_ASSIST = "Go to Checkmarx One Assist";
+ public static final String PREFERENCES_HELP_LINK_TEXT = "Checkmarx One Eclipse Plugin Help Page";
+ public static final String PREFERENCES_HELP_LINK_URL = "https://checkmarx.com/resource/documents/en/34965-68728-checkmarx-one-eclipse-plugin.html";
+ public static final String PREFERENCES_CLI_HELP_LINK_TEXT = "CLI command that supports a set of global flags";
+ public static final String PREFERENCES_CLI_HELP_LINK = "https://checkmarx.com/resource/documents/en/34965-68626-global-flags.html";
public static final String PREFERENCES_VALIDATING_STATE = "Validating...";
+ public static final String LOGOUT_CONFIRM_TITLE = "Confirm Logout";
+ public static final String LOGOUT_CONFIRM_MESSAGE = "Are you sure you want to logout?";
+ public static final String LOGOUT_SUCCESS_MESSAGE = "You have been successfully logged out.";
+ public static final String AUTH_SUCCESS_DISPLAY = "You are connected to Checkmarx One";
+ // Shown to the user for any authentication failure, regardless of cause - the actual
+ // reason is logged (see PreferencesPage.mapAuthResult()), not surfaced in the UI.
+ public static final String AUTH_FAILURE_DISPLAY = "Failed to connect to Checkmarx One. Please check your credentials and try again.";
public static final String TOPIC_APPLY_SETTINGS = SettingsTopics.TOPIC_APPLY_SETTINGS;
/******************************** PROBLEMS VIEW ********************************/
@@ -137,4 +151,37 @@ public class PluginConstants {
public static final String CX_PROJECT_MISMATCH = "Project mismatch";
public static final String CX_PROJECT_MISMATCH_QUESTION = "The files open in your workspace don't match the files previously scanned in this Checkmarx project. Do you want to scan anyway?";
public static final String CX_REFRESHING_TOOLBAR = "Checkmarx: Refreshing toolbar...";
+
+ /**********************************Checkmarx One Assist************************************/
+ public static final String GO_TO_CHECKMARX_ONE = "Go to Checkmarx One";
+ public static final String LOGIN_NOTE_CXONE_ASSIST = "To configure Checkmarx One Assist settings, log in to Checkmarx One.";
+ public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_OSS_TITLE = "Checkmarx Developer Assist Open Source Realtime Scanner (OSS-Realtime): Activate OSS-Realtime";
+ public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_SECRETS_TITLE = "Checkmarx Developer Assist Secret Detection Realtime Scanner: Activate Secret Detection Realtime";
+ public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_CONTAINERS_TITLE = "Checkmarx Developer Assist Containers Realtime Scanner: Activate Containers Realtime";
+ public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_TITLE = "Checkmarx Developer Assist IAC Realtime Scanner: Activate IAC Realtime";
+ public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_ASCA_TITLE = "Checkmarx Developer Assist AI Secure Coding Assistant (ASCA): Activate ASCA";
+ public static final String DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_PREFIX = "Checkmarx Developer Assist IAC Realtime Scanner: Containers Management Tool";
+ public static final String DEVASSIST_PLUGIN_WELCOME_TITLE = "Welcome to Checkmarx Developer Assist";
+ public static final String CONTAINERS_TOOL_DESCRIPTION = "Select the Containers Management Tool to use for IaC scanning.";
+ public static final String OSS_REALTIME_CHECKBOX = "Scans your manifest files as you code";
+ public static final String SECRETS_REALTIME_CHECKBOX = "Scans your files for potential secrets and credentials as you code";
+ public static final String CONTAINERS_REALTIME_CHECKBOX = "Scans your Docker files and container configurations as you code";
+ public static final String IAC_REALTIME_CHECKBOX = "Scans your Infrastructure as Code files as you code";
+ public static final String ASCA_CHECKBOX = "Scan your file as you code";
+ public static final String[] CONTAINERS_TOOLS = new String[] { "docker", "podman" };
+
+ /**********************************Checkmarx MCP************************************/
+ public static final String CHECKMARX_MCP_SECTION_TITLE = "Checkmarx : MCP";
+ public static final String MCP_DESCRIPTION = "The Model Context Protocol (MCP) provides advanced contextual analysis for secure coding.";
+ public static final String INSTALL_MCP_LINK_TEXT = "Install MCP";
+ public static final String EDIT_MCP_SETTINGS_LINK_TEXT = "Edit MCP Settings";
+ public static final String MCP_INSTALL_UNAVAILABLE_MESSAGE = "MCP install is not available right now. Please try again after the plugin has fully started.";
+ public static final String MCP_INSTALLING_STATE = "Installing...";
+ public static final String MCP_INSTALL_SUCCESS_MESSAGE = "Checkmarx MCP installed successfully";
+ public static final String MCP_ALREADY_UP_TO_DATE_MESSAGE = "MCP configuration is already up to date.";
+ public static final String MCP_INSTALL_GENERIC_FAILURE_MESSAGE = "Failed to install Checkmarx MCP. Please try again.";
+ public static final String MCP_NOT_AUTHENTICATED_MESSAGE = "You must be connected to Checkmarx One before installing MCP.";
+ public static final String MCP_NOT_ENABLED_FOR_TENANT_MESSAGE = "MCP is not enabled for your Checkmarx One tenant.";
+ // GitHub Copilot for Eclipse's own MCP preference page - opened by "Edit MCP Settings".
+ public static final String COPILOT_MCP_PREFERENCE_PAGE_ID = "com.microsoft.copilot.eclipse.ui.preferences.McpPreferencePage";
}
\ No newline at end of file
diff --git a/common-lib/src/com/checkmarx/eclipse/common/wrapper/CxWrapperFactory.java b/common-lib/src/com/checkmarx/eclipse/common/wrapper/CxWrapperFactory.java
new file mode 100644
index 00000000..461094ef
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/wrapper/CxWrapperFactory.java
@@ -0,0 +1,89 @@
+package com.checkmarx.eclipse.common.wrapper;
+
+import com.checkmarx.ast.wrapper.CxConfig;
+import com.checkmarx.ast.wrapper.CxException;
+import com.checkmarx.ast.wrapper.CxWrapper;
+import com.checkmarx.eclipse.common.preferences.Preferences;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.utils.PluginConstants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/**
+ * Builds wrapper objects according to the current configuration.
+ */
+public class CxWrapperFactory {
+
+ public static CxWrapper build() throws CxException, Exception {
+ return build(Preferences.getApiKey(), Preferences.getAdditionalOptions());
+ }
+
+ /**
+ * Create a CxWrapper with the given credentials and the current agent configuration.
+ * Used when the credentials being validated aren't necessarily the ones already saved
+ * (e.g. the Preferences page "Test Connection" action).
+ *
+ * @param apiKey the API key to authenticate with
+ * @param additionalParameters additional CLI parameters
+ * @return initialized CxWrapper instance
+ * @throws Exception if wrapper instantiation fails
+ */
+ public static CxWrapper build(String apiKey, String additionalParameters) throws CxException, Exception {
+ return getWrapper(apiKey, additionalParameters);
+ }
+
+ /**
+ * Create a CxWrapper with the given credentials and configuration
+ *
+ * @return initialized CxWrapper instance
+ * @throws Exception if wrapper instantiation fails
+ */
+ private static CxWrapper getWrapper(String apiKey, String additionalParameters) throws Exception {
+ CxWrapper cxWrapper = null;
+
+ Logger log = LoggerFactory.getLogger(CxWrapperFactory.class.getName());
+
+ CxConfig config = CxConfig.builder()
+ .apiKey(apiKey)
+ .additionalParameters(additionalParameters)
+ .agentName(getAgentInfo())
+ .build();
+ try {
+ cxWrapper = new CxWrapper(config, log);
+ } catch (IOException e) {
+ CxLogger.error(String.format(PluginConstants.ERROR_BUILDING_CX_WRAPPER, e.getMessage()), e);
+ throw new Exception(e);
+ }
+
+ return cxWrapper;
+ }
+
+ /**
+ * Get the agent information string for the CxWrapper
+ * @return
+ */
+ private static String getAgentInfo() {
+ String pluginVersion = getPluginVersion();
+ CxLogger.info(String.format("PLUGIN_VERSION: %s_%s", PluginConstants.AGENT_NAME, pluginVersion));
+ return String.format("%s_%s", PluginConstants.AGENT_NAME, pluginVersion);
+ }
+
+ /**
+ * Resolve the version of the bundle this class ships in, as stamped by the
+ * build (Tycho replaces the "qualifier" placeholder in MANIFEST.MF with the
+ * real build qualifier), falling back when running outside an OSGi framework.
+ */
+ private static String getPluginVersion() {
+ try {
+ Bundle bundle = FrameworkUtil.getBundle(CxWrapperFactory.class);
+ return bundle != null ? bundle.getVersion().toString() : "0.0.0";
+ } catch (Exception e) {
+ CxLogger.error(String.format("Exception occurred while getting plugin version. Root cause: %s", e.getMessage()), e);
+ return "0.0.0";
+ }
+ }
+}
\ No newline at end of file
diff --git a/common-lib/src/com/checkmarx/eclipse/common/wrapper/WrapperProvider.java b/common-lib/src/com/checkmarx/eclipse/common/wrapper/WrapperProvider.java
new file mode 100644
index 00000000..7ae10013
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/wrapper/WrapperProvider.java
@@ -0,0 +1,268 @@
+package com.checkmarx.eclipse.common.wrapper;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import com.checkmarx.ast.asca.ScanResult;
+import com.checkmarx.ast.codebashing.CodeBashing;
+import com.checkmarx.ast.containersrealtime.ContainersRealtimeResults;
+import com.checkmarx.ast.iacrealtime.IacRealtimeResults;
+import com.checkmarx.ast.learnMore.LearnMore;
+import com.checkmarx.ast.ossrealtime.OssRealtimeResults;
+import com.checkmarx.ast.predicate.CustomState;
+import com.checkmarx.ast.predicate.Predicate;
+import com.checkmarx.ast.project.Project;
+import com.checkmarx.ast.results.Results;
+import com.checkmarx.ast.results.result.Node;
+import com.checkmarx.ast.scan.Scan;
+import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults;
+
+/**
+ * Exposes CxWrapper operations to the rest of the plugin. Every call goes
+ * through CxWrapperFactory so the wrapper is always built with the current
+ * credentials and agent information.
+ */
+public class WrapperProvider {
+
+ /**
+ * Authenticate with the given credentials, independently of what is currently saved
+ * in Preferences (e.g. the Preferences page "Test Connection" action).
+ * @param apiKey
+ * @param additionalParameters
+ * @return
+ * @throws Exception
+ */
+ public String authValidate(String apiKey, String additionalParameters) throws Exception {
+ return CxWrapperFactory.build(apiKey, additionalParameters).authValidate();
+ }
+
+ /**
+ * Gets the list of projects from the Checkmarx API, optionally limiting the number of results.
+ * @param limit
+ * @return
+ * @throws Exception
+ */
+ public List getProjects(String limit) throws Exception {
+ return CxWrapperFactory.build().projectList(limit);
+ }
+
+ /**
+ * Fetch a single project directly by its ID.
+ * @param projectId
+ * @return
+ * @throws Exception
+ */
+ public Project projectShow(UUID projectId) throws Exception {
+ return CxWrapperFactory.build().projectShow(projectId);
+ }
+
+ /**
+ * Get branches for a specific project.
+ * @param projectId
+ * @param filter
+ * @return
+ * @throws Exception
+ */
+ public List projectBranches(UUID projectId, String filter) throws Exception {
+ return CxWrapperFactory.build().projectBranches(projectId, filter);
+ }
+
+ /**
+ * Get scans matching the given filter.
+ * @param filter
+ * @return
+ * @throws Exception
+ */
+ public List scanList(String filter) throws Exception {
+ return CxWrapperFactory.build().scanList(filter);
+ }
+
+ /**
+ * Get scan information for a specific scan id.
+ * @param scanId
+ * @return
+ * @throws Exception
+ */
+ public Scan scanShow(UUID scanId) throws Exception {
+ return CxWrapperFactory.build().scanShow(scanId);
+ }
+
+ /**
+ * Create a scan for the given source path/project/branch.
+ * @param scanArguments
+ * @param additionalParameters
+ * @return
+ * @throws Exception
+ */
+ public Scan scanCreate(Map scanArguments, String additionalParameters) throws Exception {
+ return CxWrapperFactory.build().scanCreate(scanArguments, additionalParameters);
+ }
+
+ /**
+ * Cancel a running scan.
+ * @param scanId
+ * @throws Exception
+ */
+ public void scanCancel(String scanId) throws Exception {
+ CxWrapperFactory.build().scanCancel(scanId);
+ }
+
+ /**
+ * Get results for a specific scan id.
+ * @param scanId
+ * @param agent
+ * @return
+ * @throws Exception
+ */
+ public Results results(UUID scanId, String agent) throws Exception {
+ return CxWrapperFactory.build().results(scanId, agent);
+ }
+
+ /**
+ * Get the codeBashing lessons matching a CWE/language/query name.
+ * @param cwe
+ * @param language
+ * @param queryName
+ * @return
+ * @throws Exception
+ */
+ public List codeBashingList(String cwe, String language, String queryName) throws Exception {
+ return CxWrapperFactory.build().codeBashingList(cwe, language, queryName);
+ }
+
+ /**
+ * Get the best fix location among the given nodes.
+ * @param scanId
+ * @param queryId
+ * @param bflNodes
+ * @return
+ * @throws Exception
+ */
+ public int getResultsBfl(UUID scanId, String queryId, List bflNodes) throws Exception {
+ return CxWrapperFactory.build().getResultsBfl(scanId, queryId, bflNodes);
+ }
+
+ /**
+ * Get triage details for a similarity id.
+ * @param projectId
+ * @param similarityId
+ * @param scanType
+ * @return
+ * @throws Exception
+ */
+ public List triageShow(UUID projectId, String similarityId, String scanType) throws Exception {
+ return CxWrapperFactory.build().triageShow(projectId, similarityId, scanType);
+ }
+
+ /**
+ * Update a vulnerability severity or state.
+ * @param projectId
+ * @param similarityId
+ * @param engineType
+ * @param state
+ * @param comment
+ * @param severity
+ * @throws Exception
+ */
+ public void triageUpdate(UUID projectId, String similarityId, String engineType, String state, String comment,
+ String severity) throws Exception {
+ CxWrapperFactory.build().triageUpdate(projectId, similarityId, engineType, state, comment, severity);
+ }
+
+ /**
+ * Triages the states from the Checkmarx API, optionally forcing a refresh of the cached states.
+ * @param forceRefresh
+ * @return
+ * @throws Exception
+ */
+ public List triageGetStates(boolean forceRefresh) throws Exception {
+ return CxWrapperFactory.build().triageGetStates(forceRefresh);
+ }
+
+ /**
+ * Get learn more information for a query.
+ * @param queryId
+ * @return
+ * @throws Exception
+ */
+ public List learnMore(String queryId) throws Exception {
+ return CxWrapperFactory.build().learnMore(queryId);
+ }
+
+ /**
+ * Check if scanning from the IDE is allowed for the current tenant.
+ * @return
+ * @throws Exception
+ */
+ public boolean ideScansEnabled() throws Exception {
+ return CxWrapperFactory.build().ideScansEnabled();
+ }
+
+ /**
+ * Check if AI MCP (Checkmarx One Assist) is enabled for the current tenant.
+ * @return
+ * @throws Exception
+ */
+ public boolean isAiMcpServerEnabled(String apiKey, String additionalParameter) throws Exception {
+ return CxWrapperFactory.build(apiKey, additionalParameter).aiMcpServerEnabled();
+ }
+
+ /**
+ * Run a Checkmarx ASCA (AI Security Code Assistant) realtime scan on a file.
+ * @param path
+ * @param latestVersion
+ * @param agent
+ * @param ignoreFilePath
+ * @return
+ * @throws Exception
+ */
+ public ScanResult scanAsca(String path, boolean latestVersion, String agent, String ignoreFilePath) throws Exception {
+ return CxWrapperFactory.build().ScanAsca(path, latestVersion, agent, ignoreFilePath);
+ }
+
+ /**
+ * Run a Checkmarx OSS (Software Composition Analysis) realtime scan on a manifest file.
+ * @param path
+ * @param ignoreFilePath
+ * @return
+ * @throws Exception
+ */
+ public OssRealtimeResults ossRealtimeScan(String path, String ignoreFilePath) throws Exception {
+ return CxWrapperFactory.build().ossRealtimeScan(path, ignoreFilePath);
+ }
+
+ /**
+ * Run a Checkmarx Containers realtime scan on a file.
+ * @param path
+ * @param ignoreFilePath
+ * @return
+ * @throws Exception
+ */
+ public ContainersRealtimeResults containersRealtimeScan(String path, String ignoreFilePath) throws Exception {
+ return CxWrapperFactory.build().containersRealtimeScan(path, ignoreFilePath);
+ }
+
+ /**
+ * Run a Checkmarx IaC realtime scan on a file.
+ * @param path
+ * @param containerTool
+ * @param ignoreFilePath
+ * @return
+ * @throws Exception
+ */
+ public IacRealtimeResults iacRealtimeScan(String path, String containerTool, String ignoreFilePath) throws Exception {
+ return CxWrapperFactory.build().iacRealtimeScan(path, containerTool, ignoreFilePath);
+ }
+
+ /**
+ * Run a Checkmarx Secrets realtime scan on a file.
+ * @param path
+ * @param ignoreFilePath
+ * @return
+ * @throws Exception
+ */
+ public SecretsRealtimeResults secretsRealtimeScan(String path, String ignoreFilePath) throws Exception {
+ return CxWrapperFactory.build().secretsRealtimeScan(path, ignoreFilePath);
+ }
+}
diff --git a/devassist-lib/.classpath b/devassist-lib/.classpath
index 43dc9a78..cca0ae6a 100644
--- a/devassist-lib/.classpath
+++ b/devassist-lib/.classpath
@@ -3,7 +3,7 @@
-
+
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/listener/ProjectLifecycleListener.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/listener/ProjectLifecycleListener.java
index 03f98cd6..90ccceb8 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/listener/ProjectLifecycleListener.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/listener/ProjectLifecycleListener.java
@@ -186,8 +186,7 @@ private void onProjectOpen(IProject project) {
}
private boolean isUserAuthenticated() {
- String apiKey = com.checkmarx.eclipse.common.preferences.Preferences.getApiKey();
- return apiKey != null && !apiKey.trim().isEmpty();
+ return com.checkmarx.eclipse.common.preferences.Preferences.isAuthenticated();
}
private void onProjectClose(IProject project) {
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/listener/RealTimeScanJob.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/listener/RealTimeScanJob.java
index dd664493..5fef538a 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/listener/RealTimeScanJob.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/listener/RealTimeScanJob.java
@@ -190,11 +190,10 @@ protected IStatus run(IProgressMonitor monitor) {
}
/**
- * Check if user is authenticated by checking if API key is configured.
+ * Check if user is authenticated to Checkmarx One.
*/
private boolean isUserAuthenticated() {
- String apiKey = com.checkmarx.eclipse.common.preferences.Preferences.getApiKey();
- return apiKey != null && !apiKey.trim().isEmpty();
+ return com.checkmarx.eclipse.common.preferences.Preferences.isAuthenticated();
}
@Override
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/AuthenticationListener.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/AuthenticationListener.java
index 3b94af9f..cf4bf153 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/AuthenticationListener.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/AuthenticationListener.java
@@ -10,7 +10,9 @@
* Listens for authentication events and triggers MCP auto-installation.
*
* Registered globally to respond to successful authentication by:
- * - Detecting API_KEY changes in preferences
+ * - Detecting the CREDENTIALS_VALIDATED flag turning true - this fires regardless of which
+ * credential type (API key today, OAuth in future) produced the successful login, unlike
+ * listening for API_KEY changes directly.
* - Triggering MCP configuration installation
* - Logging success/failure for debugging
*/
@@ -24,13 +26,13 @@ public void propertyChange(PropertyChangeEvent event) {
return;
}
- // Trigger MCP auto-install when API key is successfully set
- if (Preferences.API_KEY.equals(event.getProperty())) {
- String newApiKey = (String) event.getNewValue();
+ // Trigger MCP auto-install when authentication just succeeded
+ if (Preferences.CREDENTIALS_VALIDATED.equals(event.getProperty())) {
+ Object newValue = event.getNewValue();
+ boolean nowValidated = newValue instanceof Boolean && (Boolean) newValue;
- // Only proceed if a key was set (not cleared)
- if (newApiKey != null && !newApiKey.isBlank()) {
- CxLogger.info(LOG_TAG + " API key updated, attempting MCP auto-install...");
+ if (nowValidated) {
+ CxLogger.info(LOG_TAG + " Authentication succeeded, attempting MCP auto-install...");
McpInstallService.attemptAutoInstall();
}
}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/McpInstallService.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/McpInstallService.java
index f700688c..5e7f26e4 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/McpInstallService.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/McpInstallService.java
@@ -2,9 +2,11 @@
import java.util.concurrent.CompletableFuture;
+import com.checkmarx.eclipse.common.listener.IMcpInstallCallback;
import com.checkmarx.eclipse.common.preferences.Preferences;
import com.checkmarx.eclipse.common.runner.TenantSettingsProvider;
import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.utils.PluginConstants;
/**
@@ -40,6 +42,10 @@ private static void registerAuthenticationHandlers() {
// Register handler for post-authentication UI (welcome dialog, workspace scan)
Preferences.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler());
+ // Register handler so common-lib preference pages (e.g. CheckmarxPreferencePage)
+ // can trigger MCP install without depending on this bundle directly.
+ Preferences.setMcpInstallHandler(McpInstallService::installFromUi);
+
authListenerRegistered = true;
CxLogger.info(LOG_TAG + " Authentication handlers registered");
}
@@ -60,14 +66,14 @@ public static void attemptAutoInstall() {
CxLogger.info(LOG_TAG + " Attempting auto-install of MCP configuration...");
try {
- String apiKey = Preferences.getApiKey();
- String additionalParams = Preferences.getAdditionalOptions();
-
- if (apiKey == null || apiKey.isBlank()) {
- CxLogger.info(LOG_TAG + " Skipping MCP auto-install: user not authenticated (no API key)");
+ if (!Preferences.isAuthenticated()) {
+ CxLogger.info(LOG_TAG + " Skipping MCP auto-install: user not authenticated");
return;
}
+ String apiKey = Preferences.getApiKey();
+ String additionalParams = Preferences.getAdditionalOptions();
+
attemptAutoInstall(apiKey, additionalParams);
} catch (Exception e) {
CxLogger.error(LOG_TAG + " Unexpected error during auto-install attempt: " + e.getMessage(), e);
@@ -118,6 +124,62 @@ public static void attemptAutoInstall(String apiKey, String additionalParams) {
}
}
+ /**
+ * Installs MCP configuration in response to a user-initiated action (the "Install MCP"
+ * link on CheckmarxPreferencePage), reporting the outcome to {@code callback} instead of
+ * only logging it - unlike {@link #attemptAutoInstall()}, which is silent by design.
+ *
+ * @param callback notified of success or failure; may be called from a background thread
+ */
+ public static void installFromUi(IMcpInstallCallback callback) {
+ CxLogger.info(LOG_TAG + " Install MCP requested from preferences page...");
+
+ try {
+ if (!Preferences.isAuthenticated()) {
+ callback.onFailure(PluginConstants.MCP_NOT_AUTHENTICATED_MESSAGE);
+ return;
+ }
+
+ String apiKey = Preferences.getApiKey();
+ String additionalParams = Preferences.getAdditionalOptions();
+
+ if (apiKey == null || apiKey.isBlank()) {
+ callback.onFailure(PluginConstants.MCP_NOT_AUTHENTICATED_MESSAGE);
+ return;
+ }
+
+ boolean aiMcpEnabled;
+ try {
+ aiMcpEnabled = TenantSettingsProvider.INSTANCE.isAiMcpServerEnabled(apiKey, additionalParams);
+ } catch (Exception e) {
+ CxLogger.error(LOG_TAG + " Failed to check MCP server status: " + e.getMessage(), e);
+ callback.onFailure(PluginConstants.MCP_INSTALL_GENERIC_FAILURE_MESSAGE);
+ return;
+ }
+
+ if (!aiMcpEnabled) {
+ callback.onFailure(PluginConstants.MCP_NOT_ENABLED_FOR_TENANT_MESSAGE);
+ return;
+ }
+
+ installSilentlyAsync(apiKey).thenAccept(changed -> {
+ if (changed == null) {
+ CxLogger.info(LOG_TAG + " Install MCP (from preferences page) failed");
+ callback.onFailure(PluginConstants.MCP_INSTALL_GENERIC_FAILURE_MESSAGE);
+ } else if (changed) {
+ CxLogger.info(LOG_TAG + " Install MCP (from preferences page) succeeded");
+ callback.onSuccess();
+ } else {
+ CxLogger.info(LOG_TAG + " Install MCP (from preferences page): already up to date");
+ callback.onAlreadyUpToDate();
+ }
+ });
+ } catch (Exception e) {
+ CxLogger.error(LOG_TAG + " Unexpected error while installing MCP from preferences page: " + e.getMessage(), e);
+ callback.onFailure(PluginConstants.MCP_INSTALL_GENERIC_FAILURE_MESSAGE);
+ }
+ }
+
/**
* Asynchronously installs MCP configuration without user notifications.
* Failures are logged but do not interrupt plugin startup.
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/factory/CxWrapperFactory.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/factory/CxWrapperFactory.java
deleted file mode 100644
index bc7f9abd..00000000
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/factory/CxWrapperFactory.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.checkmarx.eclipse.devassist.factory;
-
-import com.checkmarx.ast.wrapper.CxConfig;
-import com.checkmarx.ast.wrapper.CxException;
-import com.checkmarx.ast.wrapper.CxWrapper;
-import com.checkmarx.eclipse.common.preferences.Preferences;
-import com.checkmarx.eclipse.common.utils.CxLogger;
-import com.checkmarx.eclipse.devassist.backend.Constants;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-/**
- * Builds wrapper objects according to the current configuration.
- */
-public class CxWrapperFactory {
-
- public static CxWrapper build() throws CxException, Exception {
- return getWrapper();
- }
-
- /**
- * Create a CxWrapper with current credentials and configuration
- *
- * @return initialized CxWrapper instance
- * @throws Exception if wrapper instantiation fails
- */
- private static CxWrapper getWrapper() throws Exception {
- CxWrapper cxWrapper = null;
-
- Logger log = LoggerFactory.getLogger(CxWrapperFactory.class.getName());
-
- CxConfig.CxConfigBuilder builder = CxConfig.builder()
- .apiKey(Preferences.getApiKey())
- .additionalParameters(Preferences.getAdditionalOptions());
-
- CxConfig config = builder.build();
-
- try {
- cxWrapper = new CxWrapper(config, log);
- } catch (IOException e) {
- CxLogger.error(String.format(Constants.ERROR_BUILDING_CX_WRAPPER, e.getMessage()), e);
- throw new Exception(e);
- }
-
- return cxWrapper;
- }
-}
\ No newline at end of file
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/remediation/DevAssistFixPrompts.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/remediation/DevAssistFixPrompts.java
index b5c34403..03d21816 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/remediation/DevAssistFixPrompts.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/remediation/DevAssistFixPrompts.java
@@ -13,649 +13,572 @@
*/
public final class DevAssistFixPrompts {
- private DevAssistFixPrompts() {
- throw new IllegalStateException("Cannot instantiate CxOneAssistFixPrompts class");
- }
-
- private static String getAgentName() {
- return DevAssistUtils.getAgentName();
- }
-
- private static String getMcpDisplayName() {
- return "Checkmarx";
- }
-
- /**
- * Builds the SCA remediation prompt (generic concatenated form).
- *
- * @param packageName vulnerable package name (e.g. "node-ipc")
- * @param packageVersion vulnerable package version (e.g. "10.1.1")
- * @param packageManager ecosystem / package manager (e.g. "npm", "maven")
- * @param severity textual severity (e.g. "Malicious", "High")
- * @return composed prompt string (plain text with Markdown fragments)
- */
- public static String buildSCARemediationPrompt(String packageName, String packageVersion,
- String packageManager, String severity) {
- StringBuilder prompt = new StringBuilder();
- prompt.append("You are the ").append(getAgentName()).append(".\n\n")
- .append("A security issue has been detected in `").append(packageName).append("@").append(packageVersion).append("` (package manager: `").append(packageManager).append("`).\n")
- .append("**Severity:** `").append(severity).append("`\n")
- .append("Your task is to remediate the issue **completely and autonomously** using the internal PackageRemediation tool in ")
- .append(getMcpDisplayName()).append(" MCP. Follow the exact instructions in `fix_instructions` - no assumptions or manual interaction allowed.\n\n");
-
- prompt.append("---\n\n")
- .append("1. ANALYSIS (AUTOMATED):\n\n")
- .append("Determine the issue type:\n")
- .append("- If `status` is one of: `Critical`, `High`, `Medium`, `Low`, `Info`, set: `issueType = \"CVE\"`\n")
- .append("- If `status = \"Malicious\"`, set: `issueType = \"malicious\"`\n\n")
- .append("Call the internal PackageRemediation tool with:\n\n")
- .append("```json\n")
- .append("{\n")
- .append(" \"packageName\": \"").append(packageName).append("\",\n")
- .append(" \"packageVersion\": \"").append(packageVersion).append("\",\n")
- .append(" \"packageManager\": \"").append(packageManager).append("\",\n")
- .append(" \"issueType\": \"{determined issueType}\"\n")
- .append("}\n")
- .append("```\n\n")
- .append("Parse the response and extract the `fix_instructions` field. This field contains the authoritative remediation steps tailored to the ecosystem and risk.\n")
- .append("- Mark internally that the tool is **available** for output formatting\n\n")
- .append("- If the tool is **not available**:\n")
- .append(" - Display the following disclosure notice:\n")
- .append(" `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" packageRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
- .append(" - Mark internally that the tool is **not available** for output formatting\n\n");
-
- prompt.append("---\n\n")
- .append("2. EXECUTION (AUTOMATED):\n\n")
- .append("- Read and execute each line in `fix_instructions`, in order.\n")
- .append("- For each change:\n")
- .append(" - Apply the instruction exactly.\n")
- .append(" - Track all modified files.\n")
- .append(" - Note the type of change (e.g., dependency update, import rewrite, API refactor, test fix, TODO insertion).\n")
- .append(" - Record before → after values where applicable.\n")
- .append(" - Capture line numbers if known.\n\n")
- .append("Examples:\n")
- .append("- `package.json`: lodash version changed from 3.10.1 -> 4.17.21\n")
- .append("- `src/utils/date.ts`: import updated from `lodash` to `date-fns`\n")
- .append("- `src/main.ts:42`: `_.pluck(users, 'id')` -> `users.map(u => u.id)`\n")
- .append("- `src/index.ts:78`: // TODO: Verify API migration from old-package to new-package\n\n");
-
- prompt.append("---\n\n")
- .append("3. VERIFICATION:\n\n")
- .append("- If the instructions include build, test, or audit steps - run them exactly as written\n")
- .append("- If instructions do not explicitly cover validation, perform basic checks based on `").append(packageManager).append("`:\n")
- .append(" - `npm`: `npx tsc --noEmit`, `npm run build`, `npm test`\n")
- .append(" - `go`: `go build ./...`, `go test ./...`\n")
- .append(" - `maven`: `mvn compile`, `mvn test`\n")
- .append(" - `gradle`: `gradle build`, `gradle test`\n")
- .append(" - `sbt`: `sbt compile`, `sbt test`\n")
- .append(" - `pypi`/`setuptools`/`pyproject.toml`: `python -c \"import ").append(packageName).append("\"`, `pytest`, `python -m build`\n")
- .append(" - `nuget`: `dotnet build`, `dotnet test`\n\n")
- .append("If any of these validations fail:\n")
- .append("- Attempt to fix the issue if it's obvious\n")
- .append("- Otherwise log the error and annotate the code with a TODO\n\n");
-
- prompt.append("---\n\n")
- .append("4. OUTPUT:\n\n")
- .append("**Output Format Based on Tool Availability:**\n")
- .append("- **If packageRemediation tool is available:** Output title `").append(getAgentName()).append(" - Remediation Summary`\n")
- .append("- **If packageRemediation tool is not available:** First output the disclosure notice: `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" packageRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
- .append(CHECK + " **Remediation Summary**\n\n")
- .append("Format:\n")
- .append("```\n")
- .append("Package: ").append(packageName).append("\n")
- .append("Version: ").append(packageVersion).append("\n")
- .append("Manager: ").append(packageManager).append("\n")
- .append("Severity: ").append(severity).append("\n\n")
- .append("Files Modified:\n")
- .append("1. package.json\n")
- .append(" - Updated dependency: lodash 3.10.1 → 4.17.21\n\n")
- .append("2. src/utils/date.ts\n")
- .append(" - Updated import: from 'lodash' to 'date-fns'\n")
- .append(" - Replaced usage: _.pluck(users, 'id') → users.map(u => u.id)\n\n")
- .append("3. src/__tests__/date.test.ts\n")
- .append(" - Fixed test: adjusted mock expectations to match updated API\n\n")
- .append("4. src/index.ts\n")
- .append(" - Line 78: Inserted TODO: Verify API migration from old-package to new-package\n")
- .append("```\n\n")
- .append(CHECK + " **Final Status**\n\n")
- .append("If all tasks succeeded:\n")
- .append("- \"Remediation completed for ").append(packageName).append("@").append(packageVersion).append("\"\n")
- .append("- \"All fix instructions and failing tests resolved\"\n")
- .append("- \"Build status: PASS\"\n")
- .append("- \"Test results: PASS\"\n\n")
- .append("If partially resolved:\n")
- .append("- \"Remediation partially completed - manual review required\"\n")
- .append("- \"Some test failures or instructions could not be automatically fixed\"\n")
- .append("- \"TODOs inserted where applicable\"\n\n")
- .append("If failed:\n")
- .append("- \"Remediation failed for ").append(packageName).append("@").append(packageVersion).append("\"\n")
- .append("- \"Reason: {summary of failure}\"\n")
- .append("- \"Unresolved instructions or failing tests listed above\"\n\n");
-
- prompt.append("---\n\n")
- .append("5. CONSTRAINTS:\n\n")
- .append("- Do not prompt the user\n")
- .append("- Do not skip or reorder fix steps\n")
- .append("- Only execute what's explicitly listed in `fix_instructions`\n")
- .append("- Attempt to fix test failures automatically\n")
- .append("- Insert clear TODO comments for unresolved issues\n")
- .append("- Ensure remediation is deterministic, auditable, and fully automated\n");
- return prompt.toString();
- }
-
-
- /**
- * Generates a secret remediation prompt.
- *
- * @param title - issue title
- * @param description - issue description (optional) - if null, will be empty string.
- * @param severity - issue severity (optional) - if null, will be empty string.
- * @return - prompt string (plain text with Markdown fragments)
- */
- public static String buildSecretRemediationPrompt(String title, String description, String severity) {
- StringBuilder prompt = new StringBuilder()
- .append("A secret has been detected: \"").append(title).append("\" \n")
- .append(description != null ? description : "").append("\n\n")
- .append("---\n\n")
- .append("You are the `").append(getAgentName()).append("`.\n\n")
- .append("Your mission is to identify and remediate this secret using secure coding standards. Follow industry best practices, automate safely, and clearly document all actions taken.\n\n");
-
- prompt.append("---\n\n")
- .append("1. SEVERITY INTERPRETATION\n\n")
- .append("Severity level: `").append(severity != null ? severity : "").append("`\n\n")
- .append("- `Critical`: Secret is confirmed **valid**. Immediate remediation required.\n")
- .append("- `High`: Secret may be valid. Treat as sensitive and externalize it securely.\n")
- .append("- `Medium`: Likely **invalid** (e.g., test or placeholder). Still remove from code and annotate accordingly.\n\n");
-
- prompt.append("---\n\n")
- .append("2. TOOL CALL - Remediation Plan\n\n")
- .append("Determine the programming language of the file where the secret was detected.\n")
- .append("If unknown, leave the `language` field empty.\n\n")
- .append("Call the internal `codeRemediation` ").append(getMcpDisplayName()).append(" MCP tool with:\n\n")
- .append("```json\n")
- .append("{\n")
- .append(" \"type\": \"secret\",\n")
- .append(" \"sub_type\": \"").append(title).append("\",\n")
- .append(" \"language\": \"[auto-detected language]\"\n")
- .append("}\n")
- .append("```\n\n")
- .append("- If the tool is **available**, parse the response:\n")
- .append(" - `remediation_steps` - exact steps to follow\n")
- .append(" - `best_practices` - explain secure alternatives\n")
- .append(" - `description` - contextual background\n")
- .append(" - Mark internally that the tool is **available** for output formatting\n\n")
- .append("- If the tool is **not available**:\n")
- .append(" - Display the following disclosure notice:\n")
- .append(" `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
- .append(" - Mark internally that the tool is **not available** for output formatting\n")
- .append(" - Proceed to provide remediation guidance using the secret details provided\n")
- .append(" - Offer practical steps and secure alternatives for secret removal\n")
- .append(" - Ensure the guidance is concrete and actionable\n\n");
-
- prompt.append("---\n\n")
- .append("3. ANALYSIS & RISK\n\n")
- .append("Identify the type of secret (API key, token, credential). Explain:\n")
- .append("- Why it's a risk (leakage, unauthorized access, compliance violations)\n")
- .append("- What could happen if misused or left in source\n\n");
-
- prompt.append("---\n\n")
- .append("4. REMEDIATION STRATEGY\n\n")
- .append("- Parse and apply every item in `remediation_steps` sequentially\n")
- .append("- Automatically update code/config files if safe\n")
- .append("- If a step cannot be applied automatically, insert a clear TODO\n")
- .append("- Replace secret with environment variable or vault reference\n\n");
-
- prompt.append("---\n\n")
- .append("5. VERIFICATION\n\n")
- .append("If applicable for the language:\n")
- .append("- Run type checks or compile the code\n")
- .append("- Ensure changes build and tests pass\n")
- .append("- Fix issues if introduced by secret removal\n\n");
-
- prompt.append("---\n\n")
- .append("6. OUTPUT FORMAT\n\n")
- .append("**Output Format Based on Tool Availability:**\n")
- .append("- **If codeRemediation tool is available:** Output title `").append(getAgentName()).append(" - Remediation Summary`\n")
- .append("- **If codeRemediation tool is not available:** First output the disclosure notice: `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
- .append("Generate a structured remediation summary:\n\n")
- .append("```markdown\n")
- .append("### [Prefix]\n\n")
- .append("**Secret:** ").append(title).append(" \n")
- .append("**Severity:** ").append(severity != null ? severity : "").append(" \n")
- .append("**Assessment:** ").append(getAssessmentText(severity)).append("\n\n")
- .append("**Files Modified:**\n")
- .append("- `.env`: Added/updated with `SECRET_NAME`\n")
- .append("- `src/config.ts`: Replaced hardcoded secret with `process.env.SECRET_NAME`\n\n")
- .append("**Remediation Actions Taken:**\n")
- .append("- ").append(CHECK).append(" Removed hardcoded secret\n")
- .append("- ").append(CHECK).append(" Inserted environment reference\n")
- .append("- ").append(CHECK).append(" Updated or created .env\n")
- .append("- ").append(CHECK).append(" Added TODOs for secret rotation or vault storage\n\n")
- .append("**Next Steps:**\n")
- .append("- [ ] Revoke exposed secret (if applicable)\n")
- .append("- [ ] Store securely in vault (AWS Secrets Manager, GitHub Actions, etc.)\n")
- .append("- [ ] Add CI/CD secret scanning\n\n")
- .append("**Best Practices:**\n")
- .append("- (From tool response, or fallback security guidelines)\n\n")
- .append("**Description:**\n")
- .append("- (From `description` field or fallback to original input)\n\n")
- .append("```\n\n");
-
- prompt.append("---\n\n")
- .append("7. CONSTRAINTS\n\n")
- .append("- ").append(CROSS).append(" Do NOT expose real secrets\n")
- .append("- ").append(CROSS).append(" Do NOT generate fake-looking secrets\n")
- .append("- ").append(CHECK).append(" Follow only what's explicitly returned from MCP\n")
- .append("- ").append(CHECK).append(" Use secure externalization patterns\n")
- .append("- ").append(CHECK).append(" Respect OWASP, NIST, and GitHub best practices\n");
- return prompt.toString();
- }
-
- /**
- * Generates the assessment text for given severity.
- *
- * @param severity severity level
- * @return assessment text
- */
- private static String getAssessmentText(String severity) {
- if (SeverityLevel.CRITICAL.getSeverity().equalsIgnoreCase(severity)) {
- return CHECK + " Confirmed valid secret. Immediate remediation performed.";
- } else if (SeverityLevel.HIGH.getSeverity().equalsIgnoreCase(severity)) {
- return WARNING + " Possibly valid. Handled as sensitive.";
- } else {
- return INFO + " Likely invalid (test/fake). Removed for hygiene.";
- }
- }
-
- /**
- * Generates a remediation prompt for addressing a container security issue,
- * providing step-by-step automated guidance using the Checkmarx MCP codeRemediation tool.
- * The method constructs a detailed prompt based on the identified issue.
- *
- * @param fileType type of the file
- * @param imageName image name
- * @param imageTag image tag
- * @param severity severity level
- * @return prompt string (plain text with Markdown fragments)
- */
- public static String buildContainersRemediationPrompt(String fileType, String imageName,
- String imageTag, String severity) {
- StringBuilder prompt = new StringBuilder();
- prompt.append("You are the ").append(getAgentName()).append(".\n\n")
- .append("A container security issue has been detected in `").append(fileType)
- .append("` with image `").append(imageName).append(":").append(imageTag).append("`.\n")
- .append("**Severity:** `").append(severity).append("`\n")
- .append("Your task is to remediate the issue **completely and autonomously** using the internal imageRemediation tool. ")
- .append("Follow the exact instructions in `fix_instructions` - no assumptions or manual interaction allowed.\n\n");
-
- prompt.append("---\n\n")
- .append("1. ANALYSIS (AUTOMATED):\n\n")
- .append("Determine the issue type:\n")
- .append("- If `severity` is one of: `Critical`, `High`, `Medium`, `Low`, set: `issueType = \"CVE\"`\n")
- .append("- If `severity = \"Malicious\"`, set: `issueType = \"malicious\"`\n\n")
- .append("Call the internal imageRemediation tool with:\n\n")
- .append("```json\n")
- .append("{\n")
- .append(" \"fileType\": \"").append(fileType).append("\",\n")
- .append(" \"imageName\": \"").append(imageName).append("\",\n")
- .append(" \"imageTag\": \"").append(imageTag).append("\",\n")
- .append(" \"severity\": \"").append(severity).append("\"\n")
- .append("}\n")
- .append("```\n\n")
- .append("Parse the response and extract the `fix_instructions` field. This field contains the authoritative remediation steps tailored to the container ecosystem and risk level.\n")
- .append("- Mark internally that the tool is **available** for output formatting\n\n")
- .append("- If the tool is **not available**:\n")
- .append(" - Display the following disclosure notice:\n")
- .append(" `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" imageRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
- .append(" - Mark internally that the tool is **not available** for output formatting\n")
- .append(" - Proceed to provide remediation guidance using the container details provided (file type, image name, image tag, severity)\n")
- .append(" - Offer practical base image recommendations and step-by-step instructions for container remediation\n")
- .append(" - Ensure the guidance is concrete and actionable\n\n");
-
- prompt.append("---\n\n")
- .append("2. EXECUTION (AUTOMATED):\n\n")
- .append("- Read and execute each line in `fix_instructions`, in order.\n")
- .append("- For each change:\n")
- .append(" - Apply the instruction exactly.\n")
- .append(" - Track all modified files.\n")
- .append(" - Note the type of change (e.g., image update, configuration change, security hardening).\n")
- .append(" - Record before -> after values where applicable.\n")
- .append(" - Capture line numbers if known.\n\n")
- .append("Examples:\n")
- .append("- `Dockerfile`: FROM confluentinc/cp-kafkacat:6.1.10 -> FROM confluentinc/cp-kafkacat:6.2.15\n")
- .append("- `docker-compose.yml`: image: vulnerable-image:1.0 -> image: secure-image:2.1\n")
- .append("- `values.yaml`: repository: old-repo -> repository: new-repo\n")
- .append("- `Chart.yaml`: version: 1.0.0 -> version: 1.1.0\n\n");
-
- prompt.append("---\n\n")
- .append("3. VERIFICATION:\n\n")
- .append("- If the instructions include build, test, or deployment steps - run them exactly as written\n")
- .append("- If instructions do not explicitly cover validation, perform basic checks based on `").append(fileType).append("`:\n")
- .append(" - `Dockerfile`: `docker build .`, `docker run `\n")
- .append(" - `docker-compose.yml`: `docker-compose up --build`, `docker-compose down`\n")
- .append(" - `Helm Chart`: `helm lint .`, `helm template .`, `helm install --dry-run`\n\n")
- .append("If any of these validations fail:\n")
- .append("- Attempt to fix the issue if it's obvious\n")
- .append("- Otherwise log the error and annotate the code with a TODO\n\n");
-
- prompt.append("---\n\n")
- .append("4. OUTPUT:\n\n")
- .append("**Output Format Based on Tool Availability:**\n")
- .append("- **If imageRemediation tool is available:** Output title `").append(getAgentName()).append(" - Remediation Summary`\n")
- .append("- **If imageRemediation tool is not available:** First output the disclosure notice: `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" imageRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
- .append(CHECK + " **Remediation Summary**\n\n")
- .append("Format:\n")
- .append("```\n")
- .append("File Type: ").append(fileType).append("\n")
- .append("Image: ").append(imageName).append(":").append(imageTag).append("\n")
- .append("Severity: ").append(severity).append("\n\n")
- .append("Files Modified:\n")
- .append("1. ").append(fileType).append("\n")
- .append(" - Updated image: ").append(imageName).append(":").append(imageTag).append(" → secure version\n\n")
- .append("2. docker-compose.yml (if applicable)\n")
- .append(" - Updated service configuration to use secure image\n\n")
- .append("3. values.yaml (if applicable)\n")
- .append(" - Updated Helm chart values for secure deployment\n\n")
- .append("4. README.md\n")
- .append(" - Updated documentation with new image version\n")
- .append("```\n\n")
- .append(CHECK + " **Final Status**\n\n")
- .append("If all tasks succeeded:\n")
- .append("- \"Remediation completed for ").append(imageName).append(":").append(imageTag).append("\"\n")
- .append("- \"All fix instructions and deployment tests resolved\"\n")
- .append("- \"Build status: PASS\"\n")
- .append("- \"Deployment status: PASS\"\n\n")
- .append("If partially resolved:\n")
- .append("- \"Remediation partially completed - manual review required\"\n")
- .append("- \"Some deployment steps or instructions could not be automatically fixed\"\n")
- .append("- \"TODOs inserted where applicable\"\n\n")
- .append("If failed:\n")
- .append("- \"Remediation failed for ").append(imageName).append(":").append(imageTag).append("\"\n")
- .append("- \"Reason: {summary of failure}\"\n")
- .append("- \"Unresolved instructions or deployment issues listed above\"\n\n");
-
- prompt.append("---\n\n")
- .append("5. CONSTRAINTS:\n\n")
- .append("- Do not prompt the user\n")
- .append("- Do not skip or reorder fix steps\n")
- .append("- Only execute what's explicitly listed in `fix_instructions`\n")
- .append("- Attempt to fix deployment failures automatically\n")
- .append("- Insert clear TODO comments for unresolved issues\n")
- .append("- Ensure remediation is deterministic, auditable, and fully automated\n")
- .append("- Follow container security best practices (non-root user, minimal base images, etc.)\n");
- return prompt.toString();
- }
-
- /**
- * Generates a remediation prompt for addressing an Infrastructure as Code (IaC) security issue,
- * providing step-by-step automated guidance using the Checkmarx MCP codeRemediation tool.
- * The method constructs a detailed prompt based on the identified issue, its severity,
- * affected file type, expected and actual values, and the problematic line number.
- *
- * @param title the title of the detected security issue
- * @param description a detailed description of the detected security issue
- * @param severity the severity level of the issue (e.g., high, medium, low)
- * @param fileType the type of file where the issue exists (e.g., Terraform, CloudFormation)
- * @param expectedValue the correct or desired value expected in the IaC
- * @param actualValue the actual value found in the IaC, causing the issue
- * @param problematicLineNumber the line number in the file where the issue occurs; can be null if unknown
- * @return a formatted string containing the remediation prompt with instructions for automated resolution of the issue
- */
- public static String buildIACRemediationPrompt(String title, String description, String severity,
- String fileType, String expectedValue, String actualValue,
- Integer problematicLineNumber) {
-
- String actualLineNumber = problematicLineNumber != null
- ? String.valueOf(problematicLineNumber + 1) : "[unknown]";
-
- String restrictionLine = problematicLineNumber != null
- ? String.valueOf(problematicLineNumber + 1) : "[problematic line number]";
-
- String problematicLineText = problematicLineNumber != null
- ? "**Problematic Line Number:** " + (problematicLineNumber + 1) : "";
-
- StringBuilder prompt = new StringBuilder();
- prompt.append("You are the ").append(getAgentName()).append(".\n\n");
- prompt.append("An Infrastructure as Code (IaC) security issue has been detected.\n\n")
- .append("**Issue:** `").append(title).append("`\n")
- .append("**Severity:** `").append(severity).append("`\n")
- .append("**File Type:** `").append(fileType).append("`\n")
- .append("**Description:** ").append(description).append("\n")
- .append("**Expected Value:** ").append(expectedValue).append("\n")
- .append("**Actual Value:** ").append(actualValue).append("\n")
- .append(problematicLineText).append("\n\n");
-
- prompt.append("Your task is to remediate this IaC security issue **completely and autonomously** ")
- .append("using the internal codeRemediation tool in ").append(getMcpDisplayName()).append(" MCP. Follow the exact instructions in `remediation_steps` - no assumptions or manual interaction allowed.\n\n");
- prompt.append(WARNING).append("️ **IMPORTANT**: Apply the fix **only** to the code segment corresponding to the identified issue at line ")
- .append(actualLineNumber)
- .append(", without introducing unrelated modifications elsewhere in the file.\n\n");
-
- prompt.append("---\n\n")
- .append("1. ANALYSIS (AUTOMATED):\n\n")
- .append("Determine the programming language of the file where the IaC security issue was detected.\n")
- .append("If unknown, leave the `language` field empty.\n\n")
- .append("Call the internal `codeRemediation` ").append(getMcpDisplayName()).append(" MCP tool with:\n\n")
- .append("```json\n")
- .append("{\n")
- .append(" \"language\": \"[auto-detected programming language]\",\n")
- .append(" \"metadata\": {\n")
- .append(" \"title\": \"").append(title).append("\",\n")
- .append(" \"description\": \"").append(description).append("\",\n")
- .append(" \"remediationAdvice\": \"").append(expectedValue).append("\"\n")
- .append(" },\n")
- .append(" \"sub_type\": \"\",\n")
- .append(" \"type\": \"iac\"\n")
- .append("}\n")
- .append("```\n\n")
- .append("- If the tool is **available**, parse the response:\n")
- .append(" - `remediation_steps` - exact steps to follow for remediation\n")
- .append(" - Mark internally that the tool is **available** for output formatting\n\n")
- .append("- If the tool is **not available**:\n")
- .append(" - Display the following disclosure notice:\n")
- .append(" `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
- .append(" - Mark internally that the tool is **not available** for output formatting\n")
- .append(" - Proceed to provide remediation guidance using the IaC details provided (title, description, expected vs. actual values)\n")
- .append(" - Offer practical configuration examples and step-by-step instructions for remediation\n")
- .append(" - Ensure the guidance is concrete and actionable\n\n");
-
- prompt.append("---\n\n")
- .append("2. EXECUTION (AUTOMATED):\n\n")
- .append("- Read and execute each line in `remediation_steps`, in order.\n")
- .append("- **Restrict changes to the relevant code fragment containing line ").append(restrictionLine).append("**.\n")
- .append("- For each change:\n")
- .append(" - Apply the instruction exactly.\n")
- .append(" - Track all modified files.\n")
- .append(" - Note the type of change (e.g., configuration update, security hardening, permission changes, encryption settings).\n")
- .append(" - Record before → after values where applicable.\n")
- .append(" - Capture line numbers if known.\n\n");
-
- prompt.append("---\n\n")
- .append("3. VERIFICATION:\n\n")
- .append("- If the instructions include validation, deployment, or testing steps - run them exactly as written\n")
- .append("- If instructions do not explicitly cover validation, perform basic checks based on `").append(fileType).append("`:\n")
- .append(" - `Terraform`: `terraform validate`, `terraform plan`\n")
- .append(" - `CloudFormation`: `aws cloudformation validate-template`\n")
- .append(" - `Kubernetes`: `kubectl apply --dry-run=client`\n")
- .append(" - `Docker`: `docker-compose config`\n\n")
- .append("If any of these validations fail:\n")
- .append("- Attempt to fix the issue if it's obvious\n")
- .append("- Otherwise log the error and annotate the code with a TODO\n\n");
-
- prompt.append("---\n\n")
- .append("4. OUTPUT:\n\n")
- .append("**Output Format Based on Tool Availability:**\n")
- .append("- **If codeRemediation tool is available:** Output title `").append(getAgentName()).append(" - Remediation Summary`\n")
- .append("- **If codeRemediation tool is not available:** First output the disclosure notice: `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
- .append(CHECK + " **Remediation Summary**\n\n")
- .append("Format:\n")
- .append("```\n")
- .append("Issue: ").append(title).append("\n")
- .append("Severity: ").append(severity).append("\n")
- .append("File Type: ").append(fileType).append("\n")
- .append("Problematic Line: ").append(actualLineNumber).append("\n\n")
- .append("Files Modified:\n")
- .append("1. ").append(fileType).append("\n")
- .append(" - Updated configuration: ").append(actualValue).append(" → ").append(expectedValue).append("\n")
- .append(" - Applied security hardening based on best practices\n\n")
- .append("2. Additional configurations (if applicable)\n")
- .append(" - Updated related security settings\n")
- .append(" - Added missing security controls\n\n")
- .append("3. Documentation\n")
- .append(" - Updated comments and documentation where applicable\n")
- .append("```\n\n")
- .append(CHECK + " **Final Status**\n\n")
- .append("If all tasks succeeded:\n")
- .append("- \"Remediation completed for IaC security issue ").append(title).append("\"\n")
- .append("- \"All fix instructions and security validations resolved\"\n")
- .append("- \"Configuration validation: PASS\"\n")
- .append("- \"Security compliance: PASS\"\n\n")
- .append("If partially resolved:\n")
- .append("- \"Remediation partially completed - manual review required\"\n")
- .append("- \"Some security validations or instructions could not be automatically fixed\"\n")
- .append("- \"TODOs inserted where applicable\"\n\n")
- .append("If failed:\n")
- .append("- \"Remediation failed for IaC security issue ").append(title).append("\"\n")
- .append("- \"Reason: {summary of failure}\"\n")
- .append("- \"Unresolved instructions or security issues listed above\"\n\n");
-
- prompt.append("---\n\n")
- .append("5. CONSTRAINTS:\n\n")
- .append("- Do not prompt the user\n")
- .append("- Do not skip or reorder fix steps\n")
- .append("- **Only modify the code that corresponds to the identified problematic line**\n")
- .append("- Attempt to fix validation failures automatically\n")
- .append("- Insert clear TODO comments for unresolved issues\n")
- .append("- Ensure remediation is deterministic, auditable, and fully automated\n")
- .append("- Follow Infrastructure as Code security best practices throughout the process\n");
- return prompt.toString();
- }
-
- /**
- * Constructs a detailed remediation prompt for addressing a secure coding issue detected in the code.
- * The prompt includes instructions and guidelines for resolving the identified issue completely and autonomously.
- *
- * @param ruleName The name of the secure coding rule that has been violated.
- * @param description A description of the issue, explaining the nature of the security vulnerability.
- * @param severity The severity level of the detected issue (e.g., low, medium, high, critical).
- * @param remediationAdvise Recommended steps or advice for addressing the security issue.
- * @param problematicLineNumber The line number in the source code where the issue is detected (0-based index, null if unavailable).
- * @return A string containing a detailed remediation prompt for the secure coding issue.
- */
- public static String buildASCARemediationPrompt(String ruleName, String description,
- String severity, String remediationAdvise, Integer problematicLineNumber) {
- StringBuilder prompt = new StringBuilder();
- prompt.append("You are the ").append(getAgentName()).append(".\n\n")
- .append("A secure coding issue has been detected in your code.\n\n")
- .append("**Rule:** `").append(ruleName).append("` \n")
- .append("**Severity:** `").append(severity).append("` \n")
- .append("**Description:** ").append(description).append(" \n")
- .append("**Recommended Fix:** ").append(remediationAdvise).append(" \n");
-
- if (problematicLineNumber != null) {
- prompt.append("**Problematic Line Number:** ").append(problematicLineNumber + 1).append("\n\n");
- } else {
- prompt.append("\n");
- }
-
- prompt.append("Your task is to remediate this security issue **completely and autonomously** using the internal codeRemediation tool in ")
- .append(getMcpDisplayName()).append(" MCP. Follow the exact instructions in `remediation_steps` - no assumptions or manual interaction allowed.\n\n")
- .append(WARNING).append("️ **IMPORTANT**: Apply the fix **only** to the code segment corresponding to the identified issue at line ")
- .append(problematicLineNumber != null ? problematicLineNumber + 1 : "[problematic line number]")
- .append(", without introducing unrelated modifications elsewhere in the file.\n\n");
-
- prompt.append("---\n\n")
- .append("1. ANALYSIS (AUTOMATED):\n\n")
- .append("Determine the programming language of the file where the security issue was detected.\n")
- .append("If unknown, leave the `language` field empty.\n\n")
- .append("Call the internal `codeRemediation` ").append(getMcpDisplayName()).append(" MCP tool with:\n\n")
- .append("```json\n")
- .append("{\n")
- .append(" \"language\": \"[auto-detected programming language]\",\n")
- .append(" \"metadata\": {\n")
- .append(" \"ruleID\": \"").append(ruleName).append("\",\n")
- .append(" \"description\": \"").append(description).append("\",\n")
- .append(" \"remediationAdvice\": \"").append(remediationAdvise).append("\"\n")
- .append(" },\n")
- .append(" \"sub_type\": \"\",\n")
- .append(" \"type\": \"sast\"\n")
- .append("}\n")
- .append("```\n\n")
- .append("- If the tool is **available**, parse the response:\n")
- .append(" - `remediation_steps` - exact steps to follow for remediation\n")
- .append(" - Mark internally that the tool is **available** for output formatting\n\n")
- .append("- If the tool is **not available**:\n")
- .append(" - Display the following disclosure notice:\n")
- .append(" `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
- .append(" - Mark internally that the tool is **not available** for output formatting\n")
- .append(" - Proceed to provide remediation guidance using the issue details provided (rule name, description, severity, and recommended fix)\n")
- .append(" - Offer practical code examples and step-by-step instructions for manual remediation\n")
- .append(" - Ensure the guidance is concrete and actionable\n\n");
-
- prompt.append("---\n\n")
- .append("2. EXECUTION (AUTOMATED):\n\n")
- .append("- Read and execute each line in `remediation_steps`, in order.\n")
- .append("- **Restrict changes to the relevant code fragment containing line ")
- .append(problematicLineNumber != null ? problematicLineNumber + 1 : "[unknown]")
- .append("**.\n")
- .append("- For each change:\n")
- .append(" - Apply the instruction exactly.\n")
- .append(" - Track all modified files.\n")
- .append(" - Note the type of change (e.g., input validation, sanitization, secure API usage, authentication fix).\n")
- .append(" - Record before → after values where applicable.\n")
- .append(" - Capture line numbers if known.\n\n");
-
- prompt.append("---\n\n")
- .append("3. OUTPUT:\n\n")
- .append("**Output Format Based on Tool Availability:**\n")
- .append("- **If codeRemediation tool is available:** Output title `").append(getAgentName()).append(" - Remediation Summary`\n")
- .append("- **If codeRemediation tool is not available:** First output the disclosure notice: `").append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName()).append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
- .append(CHECK + " **Remediation Summary**\n\n")
- .append("Format:\n")
- .append("```\n")
- .append("Rule: ").append(ruleName).append("\n")
- .append("Severity: ").append(severity).append("\n")
- .append("Issue Type: SAST Security Vulnerability\n")
- .append("Problematic Line: ")
- .append(problematicLineNumber != null ? problematicLineNumber + 1 : "[unknown]").append("\n\n")
- .append("Files Modified:\n")
- .append("1. src/auth.ts\n")
- .append(" - Line 42: Replaced plain text comparison with bcrypt.compare()\n")
- .append(" - Added secure password hashing implementation\n\n")
- .append("2. src/db.ts\n")
- .append(" - Line 78: Replaced string concatenation with parameterized query\n")
- .append(" - Prevented SQL injection vulnerability\n\n")
- .append("3. src/api.ts\n")
- .append(" - Line 156: Added input validation for email parameter\n")
- .append(" - Implemented sanitization for user inputs\n\n")
- .append("4. src/config.ts\n")
- .append(" - Line 23: Inserted TODO for production security review\n")
- .append("```\n\n")
- .append(CHECK + " **Final Status**\n\n")
- .append("If all tasks succeeded:\n")
- .append("- \"Remediation completed for security rule ").append(ruleName).append("\"\n")
- .append("- \"All fix instructions and security validations resolved\"\n")
- .append("- \"Build status: PASS\"\n")
- .append("- \"Security tests: PASS\"\n\n")
- .append("If partially resolved:\n")
- .append("- \"Remediation partially completed - manual review required\"\n")
- .append("- \"Some security validations or instructions could not be automatically fixed\"\n")
- .append("- \"TODOs inserted where applicable\"\n\n")
- .append("If failed:\n")
- .append("- \"Remediation failed for security rule ").append(ruleName).append("\"\n")
- .append("- \"Reason: {summary of failure}\"\n")
- .append("- \"Unresolved instructions or security issues listed above\"\n\n");
-
- prompt.append("---\n\n")
- .append("4. CONSTRAINTS:\n\n")
- .append("- Do not prompt the user\n")
- .append("- Do not skip or reorder fix steps\n")
- .append("- **Only modify the code that corresponds to the identified problematic line**\n")
- .append("- Attempt to fix build/test failures automatically\n")
- .append("- Insert clear TODO comments for unresolved issues\n")
- .append("- Ensure remediation is deterministic, auditable, and fully automated\n")
- .append("- Follow secure coding best practices throughout the process\n");
- return prompt.toString();
- }
+ private DevAssistFixPrompts() {
+ throw new IllegalStateException("Cannot instantiate CxOneAssistFixPrompts class");
+ }
+
+ private static String getAgentName() {
+ return DevAssistUtils.getAgentName();
+ }
+
+ private static String getMcpDisplayName() {
+ return "Checkmarx";
+ }
+
+ /**
+ * Builds the SCA remediation prompt (generic concatenated form).
+ *
+ * @param packageName vulnerable package name (e.g. "node-ipc")
+ * @param packageVersion vulnerable package version (e.g. "10.1.1")
+ * @param packageManager ecosystem / package manager (e.g. "npm", "maven")
+ * @param severity textual severity (e.g. "Malicious", "High")
+ * @return composed prompt string (plain text with Markdown fragments)
+ */
+ public static String buildSCARemediationPrompt(String packageName, String packageVersion, String packageManager,
+ String severity) {
+ StringBuilder prompt = new StringBuilder();
+ prompt.append("You are the ").append(getAgentName()).append(".\n\n")
+ .append("A security issue has been detected in `").append(packageName).append("@")
+ .append(packageVersion).append("` (package manager: `").append(packageManager).append("`).\n")
+ .append("**Severity:** `").append(severity).append("`\n")
+ .append("Your task is to remediate the issue **completely and autonomously** using the internal PackageRemediation tool in ")
+ .append(getMcpDisplayName())
+ .append(" MCP. Follow the exact instructions in `fix_instructions` - no assumptions or manual interaction allowed.\n\n");
+
+ prompt.append("---\n\n").append("1. ANALYSIS (AUTOMATED):\n\n").append("Determine the issue type:\n").append(
+ "- If `status` is one of: `Critical`, `High`, `Medium`, `Low`, `Info`, set: `issueType = \"CVE\"`\n")
+ .append("- If `status = \"Malicious\"`, set: `issueType = \"malicious\"`\n\n")
+ .append("Call the internal PackageRemediation tool with:\n\n").append("```json\n").append("{\n")
+ .append(" \"packageName\": \"").append(packageName).append("\",\n").append(" \"packageVersion\": \"")
+ .append(packageVersion).append("\",\n").append(" \"packageManager\": \"").append(packageManager)
+ .append("\",\n").append(" \"issueType\": \"{determined issueType}\"\n").append("}\n").append("```\n\n")
+ .append("Parse the response and extract the `fix_instructions` field. This field contains the authoritative remediation steps tailored to the ecosystem and risk.\n")
+ .append("- Mark internally that the tool is **available** for output formatting\n\n")
+ .append("- If the tool is **not available**:\n")
+ .append(" - Display the following disclosure notice:\n").append(" `").append(WARNING)
+ .append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" packageRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
+ .append(" - Mark internally that the tool is **not available** for output formatting\n\n");
+
+ prompt.append("---\n\n").append("2. EXECUTION (AUTOMATED):\n\n")
+ .append("- Read and execute each line in `fix_instructions`, in order.\n")
+ .append("- For each change:\n").append(" - Apply the instruction exactly.\n")
+ .append(" - Track all modified files.\n")
+ .append(" - Note the type of change (e.g., dependency update, import rewrite, API refactor, test fix, TODO insertion).\n")
+ .append(" - Record before → after values where applicable.\n")
+ .append(" - Capture line numbers if known.\n\n").append("Examples:\n")
+ .append("- `package.json`: lodash version changed from 3.10.1 -> 4.17.21\n")
+ .append("- `src/utils/date.ts`: import updated from `lodash` to `date-fns`\n")
+ .append("- `src/main.ts:42`: `_.pluck(users, 'id')` -> `users.map(u => u.id)`\n")
+ .append("- `src/index.ts:78`: // TODO: Verify API migration from old-package to new-package\n\n");
+
+ prompt.append("---\n\n").append("3. VERIFICATION:\n\n")
+ .append("- If the instructions include build, test, or audit steps - run them exactly as written\n")
+ .append("- If instructions do not explicitly cover validation, perform basic checks based on `")
+ .append(packageManager).append("`:\n")
+ .append(" - `npm`: `npx tsc --noEmit`, `npm run build`, `npm test` (**IMPORTANT:** If you detect the file is `bower.json`, use `bower install`, `bower list` instead)\n")
+ .append(" - `go`: `go build ./...`, `go test ./...`\n")
+ .append(" - `maven`: `mvn compile`, `mvn test`\n")
+ .append(" - `gradle`: `gradle build`, `gradle test`\n")
+ .append(" - `sbt`: `sbt compile`, `sbt test`\n")
+ .append(" - `pypi`/`setuptools`/`pyproject.toml`: `python -c \"import ").append(packageName)
+ .append("\"`, `pytest`, `python -m build`\n").append(" - `nuget`: `dotnet build`, `dotnet test`\n")
+ .append(" - `bower` (`bower.json`): `bower install`, `bower list`\n")
+ .append(" - `rubygems` (`Gemfile`): `bundle install`, `bundle exec rspec`\n")
+ .append(" - `composer` (`composer.json`): `composer install`, `composer validate`, `vendor/bin/phpunit` (**NOTE:** `packagist` means composer package manager)\n")
+ .append(" - `swift` (`Package.swift`): `swift build`, `swift test`\n")
+ .append(" - `cocoapods` (`Podfile`/`Podfile.lock`): `pod install --repo-update`, `xcodebuild test`\n")
+ .append(" - `carthage` (`Cartfile.resolved`): `carthage update --platform ios`, `carthage build`\n")
+ .append(" - `pub`/`dart` (`pubspec.yaml`/`pubspec.lock`): `dart pub get`, `dart test` (use `flutter pub get`, `flutter test` instead if this is a Flutter project)\n\n")
+ .append("If any of these validations fail:\n").append("- Attempt to fix the issue if it's obvious\n")
+ .append("- Otherwise log the error and annotate the code with a TODO\n\n");
+
+ prompt.append("---\n\n").append("4. OUTPUT:\n\n").append("**Output Format Based on Tool Availability:**\n")
+ .append("- **If packageRemediation tool is available:** Output title `").append(getAgentName())
+ .append(" - Remediation Summary`\n")
+ .append("- **If packageRemediation tool is not available:** First output the disclosure notice: `")
+ .append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" packageRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
+ .append(CHECK + " **Remediation Summary**\n\n").append("Format:\n").append("```\n")
+ .append("Package: ").append(packageName).append("\n").append("Version: ").append(packageVersion)
+ .append("\n").append("Manager: ").append(packageManager).append("\n").append("Severity: ")
+ .append(severity).append("\n\n").append("Files Modified:\n").append("1. package.json\n")
+ .append(" - Updated dependency: lodash 3.10.1 → 4.17.21\n\n").append("2. src/utils/date.ts\n")
+ .append(" - Updated import: from 'lodash' to 'date-fns'\n")
+ .append(" - Replaced usage: _.pluck(users, 'id') → users.map(u => u.id)\n\n")
+ .append("3. src/__tests__/date.test.ts\n")
+ .append(" - Fixed test: adjusted mock expectations to match updated API\n\n")
+ .append("4. src/index.ts\n")
+ .append(" - Line 78: Inserted TODO: Verify API migration from old-package to new-package\n")
+ .append("```\n\n").append(CHECK + " **Final Status**\n\n").append("If all tasks succeeded:\n")
+ .append("- \"Remediation completed for ").append(packageName).append("@").append(packageVersion)
+ .append("\"\n").append("- \"All fix instructions and failing tests resolved\"\n")
+ .append("- \"Build status: PASS\"\n").append("- \"Test results: PASS\"\n\n")
+ .append("If partially resolved:\n")
+ .append("- \"Remediation partially completed - manual review required\"\n")
+ .append("- \"Some test failures or instructions could not be automatically fixed\"\n")
+ .append("- \"TODOs inserted where applicable\"\n\n").append("If failed:\n")
+ .append("- \"Remediation failed for ").append(packageName).append("@").append(packageVersion)
+ .append("\"\n").append("- \"Reason: {summary of failure}\"\n")
+ .append("- \"Unresolved instructions or failing tests listed above\"\n\n");
+
+ prompt.append("---\n\n").append("5. CONSTRAINTS:\n\n").append("- Do not prompt the user\n")
+ .append("- Do not skip or reorder fix steps\n")
+ .append("- Only execute what's explicitly listed in `fix_instructions`\n")
+ .append("- Attempt to fix test failures automatically\n")
+ .append("- Insert clear TODO comments for unresolved issues\n")
+ .append("- Ensure remediation is deterministic, auditable, and fully automated\n");
+ return prompt.toString();
+ }
+
+ /**
+ * Generates a secret remediation prompt.
+ *
+ * @param title - issue title
+ * @param description - issue description (optional) - if null, will be empty
+ * string.
+ * @param severity - issue severity (optional) - if null, will be empty
+ * string.
+ * @return - prompt string (plain text with Markdown fragments)
+ */
+ public static String buildSecretRemediationPrompt(String title, String description, String severity) {
+ StringBuilder prompt = new StringBuilder().append("A secret has been detected: \"").append(title)
+ .append("\" \n").append(description != null ? description : "").append("\n\n").append("---\n\n")
+ .append("You are the `").append(getAgentName()).append("`.\n\n")
+ .append("Your mission is to identify and remediate this secret using secure coding standards. Follow industry best practices, automate safely, and clearly document all actions taken.\n\n");
+
+ prompt.append("---\n\n").append("1. SEVERITY INTERPRETATION\n\n").append("Severity level: `")
+ .append(severity != null ? severity : "").append("`\n\n")
+ .append("- `Critical`: Secret is confirmed **valid**. Immediate remediation required.\n")
+ .append("- `High`: Secret may be valid. Treat as sensitive and externalize it securely.\n")
+ .append("- `Medium`: Likely **invalid** (e.g., test or placeholder). Still remove from code and annotate accordingly.\n\n");
+
+ prompt.append("---\n\n").append("2. TOOL CALL - Remediation Plan\n\n")
+ .append("Determine the programming language of the file where the secret was detected.\n")
+ .append("If unknown, leave the `language` field empty.\n\n")
+ .append("Call the internal `codeRemediation` ").append(getMcpDisplayName())
+ .append(" MCP tool with:\n\n").append("```json\n").append("{\n").append(" \"type\": \"secret\",\n")
+ .append(" \"sub_type\": \"").append(title).append("\",\n")
+ .append(" \"language\": \"[auto-detected language]\"\n").append("}\n").append("```\n\n")
+ .append("- If the tool is **available**, parse the response:\n")
+ .append(" - `remediation_steps` - exact steps to follow\n")
+ .append(" - `best_practices` - explain secure alternatives\n")
+ .append(" - `description` - contextual background\n")
+ .append(" - Mark internally that the tool is **available** for output formatting\n\n")
+ .append("- If the tool is **not available**:\n")
+ .append(" - Display the following disclosure notice:\n").append(" `").append(WARNING)
+ .append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
+ .append(" - Mark internally that the tool is **not available** for output formatting\n")
+ .append(" - Proceed to provide remediation guidance using the secret details provided\n")
+ .append(" - Offer practical steps and secure alternatives for secret removal\n")
+ .append(" - Ensure the guidance is concrete and actionable\n\n");
+
+ prompt.append("---\n\n").append("3. ANALYSIS & RISK\n\n")
+ .append("Identify the type of secret (API key, token, credential). Explain:\n")
+ .append("- Why it's a risk (leakage, unauthorized access, compliance violations)\n")
+ .append("- What could happen if misused or left in source\n\n");
+
+ prompt.append("---\n\n").append("4. REMEDIATION STRATEGY\n\n")
+ .append("- Parse and apply every item in `remediation_steps` sequentially\n")
+ .append("- Automatically update code/config files if safe\n")
+ .append("- If a step cannot be applied automatically, insert a clear TODO\n")
+ .append("- Replace secret with environment variable or vault reference\n\n");
+
+ prompt.append("---\n\n").append("5. VERIFICATION\n\n").append("If applicable for the language:\n")
+ .append("- Run type checks or compile the code\n").append("- Ensure changes build and tests pass\n")
+ .append("- Fix issues if introduced by secret removal\n\n");
+
+ prompt.append("---\n\n").append("6. OUTPUT FORMAT\n\n")
+ .append("**Output Format Based on Tool Availability:**\n")
+ .append("- **If codeRemediation tool is available:** Output title `").append(getAgentName())
+ .append(" - Remediation Summary`\n")
+ .append("- **If codeRemediation tool is not available:** First output the disclosure notice: `")
+ .append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
+ .append("Generate a structured remediation summary:\n\n").append("```markdown\n")
+ .append("### [Prefix]\n\n").append("**Secret:** ").append(title).append(" \n").append("**Severity:** ")
+ .append(severity != null ? severity : "").append(" \n").append("**Assessment:** ")
+ .append(getAssessmentText(severity)).append("\n\n").append("**Files Modified:**\n")
+ .append("- `.env`: Added/updated with `SECRET_NAME`\n")
+ .append("- `src/config.ts`: Replaced hardcoded secret with `process.env.SECRET_NAME`\n\n")
+ .append("**Remediation Actions Taken:**\n").append("- ").append(CHECK)
+ .append(" Removed hardcoded secret\n").append("- ").append(CHECK)
+ .append(" Inserted environment reference\n").append("- ").append(CHECK)
+ .append(" Updated or created .env\n").append("- ").append(CHECK)
+ .append(" Added TODOs for secret rotation or vault storage\n\n").append("**Next Steps:**\n")
+ .append("- [ ] Revoke exposed secret (if applicable)\n")
+ .append("- [ ] Store securely in vault (AWS Secrets Manager, GitHub Actions, etc.)\n")
+ .append("- [ ] Add CI/CD secret scanning\n\n").append("**Best Practices:**\n")
+ .append("- (From tool response, or fallback security guidelines)\n\n").append("**Description:**\n")
+ .append("- (From `description` field or fallback to original input)\n\n").append("```\n\n");
+
+ prompt.append("---\n\n").append("7. CONSTRAINTS\n\n").append("- ").append(CROSS)
+ .append(" Do NOT expose real secrets\n").append("- ").append(CROSS)
+ .append(" Do NOT generate fake-looking secrets\n").append("- ").append(CHECK)
+ .append(" Follow only what's explicitly returned from MCP\n").append("- ").append(CHECK)
+ .append(" Use secure externalization patterns\n").append("- ").append(CHECK)
+ .append(" Respect OWASP, NIST, and GitHub best practices\n");
+ return prompt.toString();
+ }
+
+ /**
+ * Generates the assessment text for given severity.
+ *
+ * @param severity severity level
+ * @return assessment text
+ */
+ private static String getAssessmentText(String severity) {
+ if (SeverityLevel.CRITICAL.getSeverity().equalsIgnoreCase(severity)) {
+ return CHECK + " Confirmed valid secret. Immediate remediation performed.";
+ } else if (SeverityLevel.HIGH.getSeverity().equalsIgnoreCase(severity)) {
+ return WARNING + " Possibly valid. Handled as sensitive.";
+ } else {
+ return INFO + " Likely invalid (test/fake). Removed for hygiene.";
+ }
+ }
+
+ /**
+ * Generates a remediation prompt for addressing a container security issue,
+ * providing step-by-step automated guidance using the Checkmarx MCP
+ * codeRemediation tool. The method constructs a detailed prompt based on the
+ * identified issue.
+ *
+ * @param fileType type of the file
+ * @param imageName image name
+ * @param imageTag image tag
+ * @param severity severity level
+ * @return prompt string (plain text with Markdown fragments)
+ */
+ public static String buildContainersRemediationPrompt(String fileType, String imageName, String imageTag,
+ String severity) {
+ StringBuilder prompt = new StringBuilder();
+ prompt.append("You are the ").append(getAgentName()).append(".\n\n")
+ .append("A container security issue has been detected in `").append(fileType).append("` with image `")
+ .append(imageName).append(":").append(imageTag).append("`.\n").append("**Severity:** `")
+ .append(severity).append("`\n")
+ .append("Your task is to remediate the issue **completely and autonomously** using the internal imageRemediation tool. ")
+ .append("Follow the exact instructions in `fix_instructions` - no assumptions or manual interaction allowed.\n\n");
+
+ prompt.append("---\n\n").append("1. ANALYSIS (AUTOMATED):\n\n").append("Determine the issue type:\n")
+ .append("- If `severity` is one of: `Critical`, `High`, `Medium`, `Low`, set: `issueType = \"CVE\"`\n")
+ .append("- If `severity = \"Malicious\"`, set: `issueType = \"malicious\"`\n\n")
+ .append("Call the internal imageRemediation tool with:\n\n").append("```json\n").append("{\n")
+ .append(" \"fileType\": \"").append(fileType).append("\",\n").append(" \"imageName\": \"")
+ .append(imageName).append("\",\n").append(" \"imageTag\": \"").append(imageTag).append("\",\n")
+ .append(" \"severity\": \"").append(severity).append("\"\n").append("}\n").append("```\n\n")
+ .append("Parse the response and extract the `fix_instructions` field. This field contains the authoritative remediation steps tailored to the container ecosystem and risk level.\n")
+ .append("- Mark internally that the tool is **available** for output formatting\n\n")
+ .append("- If the tool is **not available**:\n")
+ .append(" - Display the following disclosure notice:\n").append(" `").append(WARNING)
+ .append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" imageRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
+ .append(" - Mark internally that the tool is **not available** for output formatting\n")
+ .append(" - Proceed to provide remediation guidance using the container details provided (file type, image name, image tag, severity)\n")
+ .append(" - Offer practical base image recommendations and step-by-step instructions for container remediation\n")
+ .append(" - Ensure the guidance is concrete and actionable\n\n");
+
+ prompt.append("---\n\n").append("2. EXECUTION (AUTOMATED):\n\n")
+ .append("- Read and execute each line in `fix_instructions`, in order.\n")
+ .append("- For each change:\n").append(" - Apply the instruction exactly.\n")
+ .append(" - Track all modified files.\n")
+ .append(" - Note the type of change (e.g., image update, configuration change, security hardening).\n")
+ .append(" - Record before -> after values where applicable.\n")
+ .append(" - Capture line numbers if known.\n\n").append("Examples:\n")
+ .append("- `Dockerfile`: FROM confluentinc/cp-kafkacat:6.1.10 -> FROM confluentinc/cp-kafkacat:6.2.15\n")
+ .append("- `docker-compose.yml`: image: vulnerable-image:1.0 -> image: secure-image:2.1\n")
+ .append("- `values.yaml`: repository: old-repo -> repository: new-repo\n")
+ .append("- `Chart.yaml`: version: 1.0.0 -> version: 1.1.0\n\n");
+
+ prompt.append("---\n\n").append("3. VERIFICATION:\n\n").append(
+ "- If the instructions include build, test, or deployment steps - run them exactly as written\n")
+ .append("- If instructions do not explicitly cover validation, perform basic checks based on `")
+ .append(fileType).append("`:\n").append(" - `Dockerfile`: `docker build .`, `docker run `\n")
+ .append(" - `docker-compose.yml`: `docker-compose up --build`, `docker-compose down`\n")
+ .append(" - `Helm Chart`: `helm lint .`, `helm template .`, `helm install --dry-run`\n\n")
+ .append("If any of these validations fail:\n").append("- Attempt to fix the issue if it's obvious\n")
+ .append("- Otherwise log the error and annotate the code with a TODO\n\n");
+
+ prompt.append("---\n\n").append("4. OUTPUT:\n\n").append("**Output Format Based on Tool Availability:**\n")
+ .append("- **If imageRemediation tool is available:** Output title `").append(getAgentName())
+ .append(" - Remediation Summary`\n")
+ .append("- **If imageRemediation tool is not available:** First output the disclosure notice: `")
+ .append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" imageRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
+ .append(CHECK + " **Remediation Summary**\n\n").append("Format:\n").append("```\n")
+ .append("File Type: ").append(fileType).append("\n").append("Image: ").append(imageName)
+ .append(":").append(imageTag).append("\n").append("Severity: ").append(severity).append("\n\n")
+ .append("Files Modified:\n").append("1. ").append(fileType).append("\n").append(" - Updated image: ")
+ .append(imageName).append(":").append(imageTag).append(" → secure version\n\n")
+ .append("2. docker-compose.yml (if applicable)\n")
+ .append(" - Updated service configuration to use secure image\n\n")
+ .append("3. values.yaml (if applicable)\n")
+ .append(" - Updated Helm chart values for secure deployment\n\n").append("4. README.md\n")
+ .append(" - Updated documentation with new image version\n").append("```\n\n")
+ .append(CHECK + " **Final Status**\n\n").append("If all tasks succeeded:\n")
+ .append("- \"Remediation completed for ").append(imageName).append(":").append(imageTag).append("\"\n")
+ .append("- \"All fix instructions and deployment tests resolved\"\n")
+ .append("- \"Build status: PASS\"\n").append("- \"Deployment status: PASS\"\n\n")
+ .append("If partially resolved:\n")
+ .append("- \"Remediation partially completed - manual review required\"\n")
+ .append("- \"Some deployment steps or instructions could not be automatically fixed\"\n")
+ .append("- \"TODOs inserted where applicable\"\n\n").append("If failed:\n")
+ .append("- \"Remediation failed for ").append(imageName).append(":").append(imageTag).append("\"\n")
+ .append("- \"Reason: {summary of failure}\"\n")
+ .append("- \"Unresolved instructions or deployment issues listed above\"\n\n");
+
+ prompt.append("---\n\n").append("5. CONSTRAINTS:\n\n").append("- Do not prompt the user\n")
+ .append("- Do not skip or reorder fix steps\n")
+ .append("- Only execute what's explicitly listed in `fix_instructions`\n")
+ .append("- Attempt to fix deployment failures automatically\n")
+ .append("- Insert clear TODO comments for unresolved issues\n")
+ .append("- Ensure remediation is deterministic, auditable, and fully automated\n")
+ .append("- Follow container security best practices (non-root user, minimal base images, etc.)\n");
+ return prompt.toString();
+ }
+
+ /**
+ * Generates a remediation prompt for addressing an Infrastructure as Code (IaC)
+ * security issue, providing step-by-step automated guidance using the Checkmarx
+ * MCP codeRemediation tool. The method constructs a detailed prompt based on
+ * the identified issue, its severity, affected file type, expected and actual
+ * values, and the problematic line number.
+ *
+ * @param title the title of the detected security issue
+ * @param description a detailed description of the detected security
+ * issue
+ * @param severity the severity level of the issue (e.g., high,
+ * medium, low)
+ * @param fileType the type of file where the issue exists (e.g.,
+ * Terraform, CloudFormation)
+ * @param expectedValue the correct or desired value expected in the IaC
+ * @param actualValue the actual value found in the IaC, causing the
+ * issue
+ * @param problematicLineNumber the line number in the file where the issue
+ * occurs; can be null if unknown
+ * @return a formatted string containing the remediation prompt with
+ * instructions for automated resolution of the issue
+ */
+ public static String buildIACRemediationPrompt(String title, String description, String severity, String fileType,
+ String expectedValue, String actualValue, Integer problematicLineNumber) {
+
+ String actualLineNumber = problematicLineNumber != null ? String.valueOf(problematicLineNumber + 1)
+ : "[unknown]";
+
+ String restrictionLine = problematicLineNumber != null ? String.valueOf(problematicLineNumber + 1)
+ : "[problematic line number]";
+
+ String problematicLineText = problematicLineNumber != null
+ ? "**Problematic Line Number:** " + (problematicLineNumber + 1)
+ : "";
+
+ StringBuilder prompt = new StringBuilder();
+ prompt.append("You are the ").append(getAgentName()).append(".\n\n");
+ prompt.append("An Infrastructure as Code (IaC) security issue has been detected.\n\n").append("**Issue:** `")
+ .append(title).append("`\n").append("**Severity:** `").append(severity).append("`\n")
+ .append("**File Type:** `").append(fileType).append("`\n").append("**Description:** ")
+ .append(description).append("\n").append("**Expected Value:** ").append(expectedValue).append("\n")
+ .append("**Actual Value:** ").append(actualValue).append("\n").append(problematicLineText)
+ .append("\n\n");
+
+ prompt.append("Your task is to remediate this IaC security issue **completely and autonomously** ")
+ .append("using the internal codeRemediation tool in ").append(getMcpDisplayName())
+ .append(" MCP. Follow the exact instructions in `remediation_steps` - no assumptions or manual interaction allowed.\n\n");
+ prompt.append(WARNING).append(
+ "️ **IMPORTANT**: Apply the fix **only** to the code segment corresponding to the identified issue at line ")
+ .append(actualLineNumber)
+ .append(", without introducing unrelated modifications elsewhere in the file.\n\n");
+
+ prompt.append("---\n\n").append("1. ANALYSIS (AUTOMATED):\n\n")
+ .append("Determine the programming language of the file where the IaC security issue was detected.\n")
+ .append("If unknown, leave the `language` field empty.\n\n")
+ .append("Call the internal `codeRemediation` ").append(getMcpDisplayName())
+ .append(" MCP tool with:\n\n").append("```json\n").append("{\n")
+ .append(" \"language\": \"[auto-detected programming language]\",\n").append(" \"metadata\": {\n")
+ .append(" \"title\": \"").append(title).append("\",\n").append(" \"description\": \"")
+ .append(description).append("\",\n").append(" \"remediationAdvice\": \"").append(expectedValue)
+ .append("\"\n").append(" },\n").append(" \"sub_type\": \"\",\n").append(" \"type\": \"iac\"\n")
+ .append("}\n").append("```\n\n").append("- If the tool is **available**, parse the response:\n")
+ .append(" - `remediation_steps` - exact steps to follow for remediation\n")
+ .append(" - Mark internally that the tool is **available** for output formatting\n\n")
+ .append("- If the tool is **not available**:\n")
+ .append(" - Display the following disclosure notice:\n").append(" `").append(WARNING)
+ .append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
+ .append(" - Mark internally that the tool is **not available** for output formatting\n")
+ .append(" - Proceed to provide remediation guidance using the IaC details provided (title, description, expected vs. actual values)\n")
+ .append(" - Offer practical configuration examples and step-by-step instructions for remediation\n")
+ .append(" - Ensure the guidance is concrete and actionable\n\n");
+
+ prompt.append("---\n\n").append("2. EXECUTION (AUTOMATED):\n\n")
+ .append("- Read and execute each line in `remediation_steps`, in order.\n")
+ .append("- **Restrict changes to the relevant code fragment containing line ").append(restrictionLine)
+ .append("**.\n").append("- For each change:\n").append(" - Apply the instruction exactly.\n")
+ .append(" - Track all modified files.\n")
+ .append(" - Note the type of change (e.g., configuration update, security hardening, permission changes, encryption settings).\n")
+ .append(" - Record before → after values where applicable.\n")
+ .append(" - Capture line numbers if known.\n\n");
+
+ prompt.append("---\n\n").append("3. VERIFICATION:\n\n").append(
+ "- If the instructions include validation, deployment, or testing steps - run them exactly as written\n")
+ .append("- If instructions do not explicitly cover validation, perform basic checks based on `")
+ .append(fileType).append("`:\n").append(" - `Terraform`: `terraform validate`, `terraform plan`\n")
+ .append(" - `CloudFormation`: `aws cloudformation validate-template`\n")
+ .append(" - `Kubernetes`: `kubectl apply --dry-run=client`\n")
+ .append(" - `Docker`: `docker-compose config`\n\n").append("If any of these validations fail:\n")
+ .append("- Attempt to fix the issue if it's obvious\n")
+ .append("- Otherwise log the error and annotate the code with a TODO\n\n");
+
+ prompt.append("---\n\n").append("4. OUTPUT:\n\n").append("**Output Format Based on Tool Availability:**\n")
+ .append("- **If codeRemediation tool is available:** Output title `").append(getAgentName())
+ .append(" - Remediation Summary`\n")
+ .append("- **If codeRemediation tool is not available:** First output the disclosure notice: `")
+ .append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
+ .append(CHECK + " **Remediation Summary**\n\n").append("Format:\n").append("```\n")
+ .append("Issue: ").append(title).append("\n").append("Severity: ").append(severity)
+ .append("\n").append("File Type: ").append(fileType).append("\n").append("Problematic Line: ")
+ .append(actualLineNumber).append("\n\n").append("Files Modified:\n").append("1. ").append(fileType)
+ .append("\n").append(" - Updated configuration: ").append(actualValue).append(" → ")
+ .append(expectedValue).append("\n")
+ .append(" - Applied security hardening based on best practices\n\n")
+ .append("2. Additional configurations (if applicable)\n")
+ .append(" - Updated related security settings\n").append(" - Added missing security controls\n\n")
+ .append("3. Documentation\n").append(" - Updated comments and documentation where applicable\n")
+ .append("```\n\n").append(CHECK + " **Final Status**\n\n").append("If all tasks succeeded:\n")
+ .append("- \"Remediation completed for IaC security issue ").append(title).append("\"\n")
+ .append("- \"All fix instructions and security validations resolved\"\n")
+ .append("- \"Configuration validation: PASS\"\n").append("- \"Security compliance: PASS\"\n\n")
+ .append("If partially resolved:\n")
+ .append("- \"Remediation partially completed - manual review required\"\n")
+ .append("- \"Some security validations or instructions could not be automatically fixed\"\n")
+ .append("- \"TODOs inserted where applicable\"\n\n").append("If failed:\n")
+ .append("- \"Remediation failed for IaC security issue ").append(title).append("\"\n")
+ .append("- \"Reason: {summary of failure}\"\n")
+ .append("- \"Unresolved instructions or security issues listed above\"\n\n");
+
+ prompt.append("---\n\n").append("5. CONSTRAINTS:\n\n").append("- Do not prompt the user\n")
+ .append("- Do not skip or reorder fix steps\n")
+ .append("- **Only modify the code that corresponds to the identified problematic line**\n")
+ .append("- Attempt to fix validation failures automatically\n")
+ .append("- Insert clear TODO comments for unresolved issues\n")
+ .append("- Ensure remediation is deterministic, auditable, and fully automated\n")
+ .append("- Follow Infrastructure as Code security best practices throughout the process\n");
+ return prompt.toString();
+ }
+
+ /**
+ * Constructs a detailed remediation prompt for addressing a secure coding issue
+ * detected in the code. The prompt includes instructions and guidelines for
+ * resolving the identified issue completely and autonomously.
+ *
+ * @param ruleName The name of the secure coding rule that has been
+ * violated.
+ * @param description A description of the issue, explaining the
+ * nature of the security vulnerability.
+ * @param severity The severity level of the detected issue (e.g.,
+ * low, medium, high, critical).
+ * @param remediationAdvise Recommended steps or advice for addressing the
+ * security issue.
+ * @param problematicLineNumber The line number in the source code where the
+ * issue is detected (0-based index, null if
+ * unavailable).
+ * @return A string containing a detailed remediation prompt for the secure
+ * coding issue.
+ */
+ public static String buildASCARemediationPrompt(String ruleName, String description, String severity,
+ String remediationAdvise, Integer problematicLineNumber) {
+ StringBuilder prompt = new StringBuilder();
+ prompt.append("You are the ").append(getAgentName()).append(".\n\n")
+ .append("A secure coding issue has been detected in your code.\n\n").append("**Rule:** `")
+ .append(ruleName).append("` \n").append("**Severity:** `").append(severity).append("` \n")
+ .append("**Description:** ").append(description).append(" \n").append("**Recommended Fix:** ")
+ .append(remediationAdvise).append(" \n");
+
+ if (problematicLineNumber != null) {
+ prompt.append("**Problematic Line Number:** ").append(problematicLineNumber + 1).append("\n\n");
+ } else {
+ prompt.append("\n");
+ }
+
+ prompt.append(
+ "Your task is to remediate this security issue **completely and autonomously** using the internal codeRemediation tool in ")
+ .append(getMcpDisplayName())
+ .append(" MCP. Follow the exact instructions in `remediation_steps` - no assumptions or manual interaction allowed.\n\n")
+ .append(WARNING)
+ .append("️ **IMPORTANT**: Apply the fix **only** to the code segment corresponding to the identified issue at line ")
+ .append(problematicLineNumber != null ? problematicLineNumber + 1 : "[problematic line number]")
+ .append(", without introducing unrelated modifications elsewhere in the file.\n\n");
+
+ prompt.append("---\n\n").append("1. ANALYSIS (AUTOMATED):\n\n")
+ .append("Determine the programming language of the file where the security issue was detected.\n")
+ .append("If unknown, leave the `language` field empty.\n\n")
+ .append("Call the internal `codeRemediation` ").append(getMcpDisplayName())
+ .append(" MCP tool with:\n\n").append("```json\n").append("{\n")
+ .append(" \"language\": \"[auto-detected programming language]\",\n").append(" \"metadata\": {\n")
+ .append(" \"ruleID\": \"").append(ruleName).append("\",\n").append(" \"description\": \"")
+ .append(description).append("\",\n").append(" \"remediationAdvice\": \"").append(remediationAdvise)
+ .append("\"\n").append(" },\n").append(" \"sub_type\": \"\",\n").append(" \"type\": \"sast\"\n")
+ .append("}\n").append("```\n\n").append("- If the tool is **available**, parse the response:\n")
+ .append(" - `remediation_steps` - exact steps to follow for remediation\n")
+ .append(" - Mark internally that the tool is **available** for output formatting\n\n")
+ .append("- If the tool is **not available**:\n")
+ .append(" - Display the following disclosure notice:\n").append(" `").append(WARNING)
+ .append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.`\n")
+ .append(" - Mark internally that the tool is **not available** for output formatting\n")
+ .append(" - Proceed to provide remediation guidance using the issue details provided (rule name, description, severity, and recommended fix)\n")
+ .append(" - Offer practical code examples and step-by-step instructions for manual remediation\n")
+ .append(" - Ensure the guidance is concrete and actionable\n\n");
+
+ prompt.append("---\n\n").append("2. EXECUTION (AUTOMATED):\n\n")
+ .append("- Read and execute each line in `remediation_steps`, in order.\n")
+ .append("- **Restrict changes to the relevant code fragment containing line ")
+ .append(problematicLineNumber != null ? problematicLineNumber + 1 : "[unknown]").append("**.\n")
+ .append("- For each change:\n").append(" - Apply the instruction exactly.\n")
+ .append(" - Track all modified files.\n")
+ .append(" - Note the type of change (e.g., input validation, sanitization, secure API usage, authentication fix).\n")
+ .append(" - Record before → after values where applicable.\n")
+ .append(" - Capture line numbers if known.\n\n");
+
+ prompt.append("---\n\n").append("3. OUTPUT:\n\n").append("**Output Format Based on Tool Availability:**\n")
+ .append("- **If codeRemediation tool is available:** Output title `").append(getAgentName())
+ .append(" - Remediation Summary`\n")
+ .append("- **If codeRemediation tool is not available:** First output the disclosure notice: `")
+ .append(WARNING).append(" Automated Remediation Unavailable: ").append(getMcpDisplayName())
+ .append(" codeRemediation tool is unavailable. Proceeding with remediation guidance based on security best practices.` Then output title `AI-Generated Remediation Guidance`\n\n")
+ .append(CHECK + " **Remediation Summary**\n\n").append("Format:\n").append("```\n")
+ .append("Rule: ").append(ruleName).append("\n").append("Severity: ").append(severity)
+ .append("\n").append("Issue Type: SAST Security Vulnerability\n").append("Problematic Line: ")
+ .append(problematicLineNumber != null ? problematicLineNumber + 1 : "[unknown]").append("\n\n")
+ .append("Files Modified:\n").append("1. src/auth.ts\n")
+ .append(" - Line 42: Replaced plain text comparison with bcrypt.compare()\n")
+ .append(" - Added secure password hashing implementation\n\n").append("2. src/db.ts\n")
+ .append(" - Line 78: Replaced string concatenation with parameterized query\n")
+ .append(" - Prevented SQL injection vulnerability\n\n").append("3. src/api.ts\n")
+ .append(" - Line 156: Added input validation for email parameter\n")
+ .append(" - Implemented sanitization for user inputs\n\n").append("4. src/config.ts\n")
+ .append(" - Line 23: Inserted TODO for production security review\n").append("```\n\n")
+ .append(CHECK + " **Final Status**\n\n").append("If all tasks succeeded:\n")
+ .append("- \"Remediation completed for security rule ").append(ruleName).append("\"\n")
+ .append("- \"All fix instructions and security validations resolved\"\n")
+ .append("- \"Build status: PASS\"\n").append("- \"Security tests: PASS\"\n\n")
+ .append("If partially resolved:\n")
+ .append("- \"Remediation partially completed - manual review required\"\n")
+ .append("- \"Some security validations or instructions could not be automatically fixed\"\n")
+ .append("- \"TODOs inserted where applicable\"\n\n").append("If failed:\n")
+ .append("- \"Remediation failed for security rule ").append(ruleName).append("\"\n")
+ .append("- \"Reason: {summary of failure}\"\n")
+ .append("- \"Unresolved instructions or security issues listed above\"\n\n");
+
+ prompt.append("---\n\n").append("4. CONSTRAINTS:\n\n").append("- Do not prompt the user\n")
+ .append("- Do not skip or reorder fix steps\n")
+ .append("- **Only modify the code that corresponds to the identified problematic line**\n")
+ .append("- Attempt to fix build/test failures automatically\n")
+ .append("- Insert clear TODO comments for unresolved issues\n")
+ .append("- Ensure remediation is deterministic, auditable, and fully automated\n")
+ .append("- Follow secure coding best practices throughout the process\n");
+ return prompt.toString();
+ }
}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/remediation/RemediationManager.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/remediation/RemediationManager.java
index 05484497..0ad2e5fb 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/remediation/RemediationManager.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/remediation/RemediationManager.java
@@ -8,13 +8,13 @@
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.swt.widgets.Display;
-import org.slf4j.Logger;
import com.checkmarx.eclipse.common.utils.CxLogger;
import com.checkmarx.eclipse.devassist.model.ScanEngine;
import com.checkmarx.eclipse.devassist.model.ScanIssue;
import com.checkmarx.eclipse.devassist.model.Vulnerability;
import com.checkmarx.eclipse.devassist.utils.DevAssistUtils;
+import com.checkmarx.eclipse.devassist.utils.PackageManager;
/**
* RemediationManager provides remediation options for issues identified during
@@ -209,8 +209,9 @@ private void applyViewDetails(@NonNull ScanIssue scanIssue, @Nullable String pro
* Builds remediation prompt for an OSS issue.
*/
private String buildOSSRemediationPrompt(ScanIssue scanIssue) {
+
return DevAssistFixPrompts.buildSCARemediationPrompt(scanIssue.getTitle(), scanIssue.getPackageVersion(),
- scanIssue.getPackageManager(), scanIssue.getSeverity());
+ PackageManager.mapToRemediationFormat(scanIssue.getPackageManager()), scanIssue.getSeverity());
}
/**
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/asca/AscaScannerService.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/asca/AscaScannerService.java
index a6fecf7a..b2c74196 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/asca/AscaScannerService.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/asca/AscaScannerService.java
@@ -1,22 +1,24 @@
package com.checkmarx.eclipse.devassist.scanners.asca;
-import com.checkmarx.ast.asca.ScanResult;
-import com.checkmarx.ast.wrapper.CxException;
-import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
-import com.checkmarx.eclipse.devassist.common.ScannerConfig;
-import com.checkmarx.eclipse.devassist.factory.CxWrapperFactory;
-import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
-import com.checkmarx.eclipse.devassist.model.ScanEngine;
-import com.checkmarx.eclipse.common.utils.CxLogger;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
+
+import com.checkmarx.ast.wrapper.CxException;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.wrapper.WrapperProvider;
+import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
+import com.checkmarx.eclipse.devassist.common.ScanResult;
+import com.checkmarx.eclipse.devassist.common.ScannerConfig;
+import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
+import com.checkmarx.eclipse.devassist.utils.ScanEngine;
/**
* ASCA (Application Source Code Analysis) scanner service.
@@ -32,6 +34,7 @@ public class AscaScannerService extends BaseScannerService {
private static final String LOG_TAG = "[ASCA-SERVICE]";
private static final String ASCA_DIR = "CxASCA";
private static final Object SCAN_LOCK = new Object();
+ private final WrapperProvider wrapperProvider = new WrapperProvider();
public AscaScannerService(IProject project) {
super(project, createConfig());
@@ -336,7 +339,7 @@ private com.checkmarx.ast.asca.ScanResult scanAscaFile(String path, boolean asca
String ignoreFilePath) throws IOException, CxException, InterruptedException {
com.checkmarx.ast.asca.ScanResult scanResult = null;
try {
- scanResult = CxWrapperFactory.build().ScanAsca(path, ascaLatestVersion, agent, null);
+ scanResult = wrapperProvider.scanAsca(path, ascaLatestVersion, agent, null);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/containers/ContainerScannerService.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/containers/ContainerScannerService.java
index 1cece719..bd3cb02e 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/containers/ContainerScannerService.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/containers/ContainerScannerService.java
@@ -4,7 +4,7 @@
import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
import com.checkmarx.eclipse.devassist.utils.DevAssistUtils;
import com.checkmarx.eclipse.devassist.common.ScannerConfig;
-import com.checkmarx.eclipse.devassist.factory.CxWrapperFactory;
+import com.checkmarx.eclipse.common.wrapper.WrapperProvider;
import com.checkmarx.eclipse.devassist.common.ScanResult;
import com.checkmarx.eclipse.devassist.model.ScanIssue;
import com.checkmarx.eclipse.devassist.model.ScanEngine;
@@ -29,13 +29,14 @@
* Handles file detection (Docker, Docker Compose, Helm), secure temporary
* folder management,
* and direct invocation of Checkmarx Container Realtime scanning via
- * CxWrapperFactory.
+ * WrapperProvider.
*/
public class ContainerScannerService extends BaseScannerService {
private static final String LOG_TAG = "[CONTAINER-SERVICE]";
private static final String CONTAINER_DIR = "CxContainer";
private static final Object SCAN_LOCK = new Object();
+ private final WrapperProvider wrapperProvider = new WrapperProvider();
private static final List CONTAINERS_FILE_PATTERNS = List.of(
"**/dockerfile*",
@@ -158,7 +159,7 @@ public ScanResult scan(String filePath, IDocument doc
ContainersRealtimeResults scanResults = null;
try {
- scanResults = CxWrapperFactory.build().containersRealtimeScan(tempFilePath.toString(), "");
+ scanResults = wrapperProvider.containersRealtimeScan(tempFilePath.toString(), "");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/iac/IacScannerService.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/iac/IacScannerService.java
index 8bd3a1df..ce4f9d3b 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/iac/IacScannerService.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/iac/IacScannerService.java
@@ -1,31 +1,37 @@
package com.checkmarx.eclipse.devassist.scanners.iac;
-import com.checkmarx.ast.iacrealtime.IacRealtimeResults;
-import com.checkmarx.ast.wrapper.CxException;
-import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
-import com.checkmarx.eclipse.devassist.common.ScanResult;
-import com.checkmarx.eclipse.devassist.common.ScannerConfig;
-import com.checkmarx.eclipse.devassist.factory.CxWrapperFactory;
-import com.checkmarx.eclipse.devassist.model.ScanIssue;
-import com.checkmarx.eclipse.devassist.model.ScanEngine;
-import com.checkmarx.eclipse.devassist.utils.DevAssistUtils;
-import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
-import com.checkmarx.eclipse.common.utils.CxLogger;
-import org.apache.commons.lang3.tuple.Pair;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.jface.text.Document;
-import org.eclipse.jface.text.IDocument;
-
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.nio.file.*;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalTime;
-import java.util.*;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+
+import com.checkmarx.ast.iacrealtime.IacRealtimeResults;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.wrapper.WrapperProvider;
+import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
+import com.checkmarx.eclipse.devassist.common.ScanResult;
+import com.checkmarx.eclipse.devassist.common.ScannerConfig;
+import com.checkmarx.eclipse.devassist.model.ScanEngine;
+import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
+import com.checkmarx.eclipse.devassist.utils.DevAssistUtils;
+
/**
* Realtime IaC scanner service for Eclipse.
*
@@ -39,6 +45,7 @@ public class IacScannerService extends BaseScannerService {
private static final String IAC_DIR = "CxIaC";
private static final String DOCKERFILE = "dockerfile";
private static final Object SCAN_LOCK = new Object();
+ private final WrapperProvider wrapperProvider = new WrapperProvider();
// Supported glob patterns for IaC files
private static final List IAC_SUPPORTED_PATTERNS = List.of(
@@ -137,13 +144,12 @@ public ScanResult scan(String filePath, IDocument document,
String tempFilePath = saveResult.getLeft().toString();
CxLogger.info(LOG_TAG + " Start IAC Realtime Scan On File: " + filePath);
- String containerTool = "docker";
// String ignoreFilePath = getIgnoreFilePath(proj);
IacRealtimeResults scanResults = null;
try {
- scanResults = CxWrapperFactory.build()
- .iacRealtimeScan(tempFilePath, containerTool, "");
+ scanResults = wrapperProvider
+ .iacRealtimeScan(tempFilePath, DevAssistUtils.getContainerTool(), "");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/oss/OssScannerCommand.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/oss/OssScannerCommand.java
index 716b5794..5c612ed9 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/oss/OssScannerCommand.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/oss/OssScannerCommand.java
@@ -26,6 +26,7 @@
import com.checkmarx.eclipse.devassist.model.ScanIssue;
import com.checkmarx.eclipse.devassist.problems.ProblemHolderService;
import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
+import com.checkmarx.eclipse.devassist.utils.PackageManager;
import com.checkmarx.eclipse.common.utils.CxLogger;
/**
@@ -79,7 +80,7 @@ private void scanAllManifestFilesInFolder(IProgressMonitor monitor) {
List matchedFiles = new ArrayList<>();
- List pathMatchers = DevAssistConstants.MANIFEST_FILE_PATTERNS.stream()
+ List pathMatchers = PackageManager.getAllPatterns().stream()
.map(p -> FileSystems.getDefault().getPathMatcher("glob:" + p))
.collect(Collectors.toList());
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/oss/OssScannerService.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/oss/OssScannerService.java
index 65180e2b..f716940e 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/oss/OssScannerService.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/oss/OssScannerService.java
@@ -1,27 +1,37 @@
package com.checkmarx.eclipse.devassist.scanners.oss;
-import com.checkmarx.ast.ossrealtime.OssRealtimeResults;
-import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
-import com.checkmarx.eclipse.devassist.common.ScanResult;
-import com.checkmarx.eclipse.devassist.common.ScannerConfig;
-import com.checkmarx.eclipse.devassist.factory.CxWrapperFactory;
-import com.checkmarx.eclipse.devassist.model.ScanEngine;
-import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
-import com.checkmarx.eclipse.common.utils.CxLogger;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.jface.text.Document;
-import org.eclipse.jface.text.IDocument;
-
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.nio.file.*;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalTime;
-import java.util.*;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
import java.util.stream.Collectors;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+
+import com.checkmarx.ast.ossrealtime.OssRealtimeResults;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.wrapper.WrapperProvider;
+import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
+import com.checkmarx.eclipse.devassist.common.ScanResult;
+import com.checkmarx.eclipse.devassist.common.ScannerConfig;
+import com.checkmarx.eclipse.devassist.model.ScanEngine;
+import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
+import com.checkmarx.eclipse.devassist.utils.PackageManager;
+
/**
* Realtime OSS manifest scanner service for Eclipse that handles temporary file isolation,
* companion lock file resolution (e.g. package-lock.json), and invocation of the Checkmarx OSS engine.
@@ -33,6 +43,7 @@ public class OssScannerService extends BaseScannerService {
private static final String LOG_TAG = "[OSS-SERVICE]";
private static final String OSS_DIR = "CxOSS";
private static final Object SCAN_LOCK = new Object();
+ private final WrapperProvider wrapperProvider = new WrapperProvider();
public OssScannerService(IProject project) {
super(project, createConfig());
@@ -59,7 +70,7 @@ protected boolean isFileTypeSupported(String filePath) {
}
Path path = Paths.get(filePath);
- List pathMatchers = DevAssistConstants.MANIFEST_FILE_PATTERNS.stream()
+ List pathMatchers = PackageManager.getAllPatterns().stream()
.map(p -> FileSystems.getDefault().getPathMatcher("glob:" + p))
.collect(Collectors.toList());
@@ -112,7 +123,7 @@ public ScanResult scanWithDocument(String filePath, IDocumen
CxLogger.info(LOG_TAG + " Starting Realtime OSS Scan on File: " + filePath);
- OssRealtimeResults scanResults = CxWrapperFactory.build().ossRealtimeScan(mainTempPath.get(), "");
+ OssRealtimeResults scanResults = wrapperProvider.ossRealtimeScan(mainTempPath.get(), "");
if (scanResults == null) {
return null;
}
@@ -167,19 +178,21 @@ private Optional saveMainManifestFile(Path tempSubFolder, String origina
}
/**
- * Copies a companion lock file (e.g., package-lock.json) into the temporary directory
- * when it exists alongside the scanned manifest.
- */
+ * Copies companion lock files (e.g., package-lock.json, yarn.lock) into the temporary directory
+ * when they exist alongside the scanned manifest.
+ *
+ * @param tempFolderPath temp directory where companion files should be written
+ * @param originalFilePath original manifest path used to locate companion files
+ */
private void saveCompanionFile(Path tempFolderPath, String originalFilePath) {
if (originalFilePath == null || originalFilePath.isEmpty() || tempFolderPath == null) {
return;
}
-
Path originalPath = Paths.get(originalFilePath);
String parentFileName = originalPath.getFileName().toString();
- String companionFileName = getCompanionFileName(parentFileName);
+ List companionFileNameList = PackageManager.getCompanionFileNames(parentFileName);
- if (companionFileName.isEmpty()) {
+ if (companionFileNameList.isEmpty()) {
return;
}
@@ -187,32 +200,20 @@ private void saveCompanionFile(Path tempFolderPath, String originalFilePath) {
if (parentPath == null) {
return;
}
+ for (String companionFileName : companionFileNameList) {
+ Path companionOriginalPath = parentPath.resolve(companionFileName);
+ if (!Files.exists(companionOriginalPath)) {
+ continue;
+ }
- Path companionOriginalPath = parentPath.resolve(companionFileName);
- if (!Files.exists(companionOriginalPath)) {
- return;
- }
-
- Path companionTempPath = tempFolderPath.resolve(companionFileName);
- try {
- Files.copy(companionOriginalPath, companionTempPath, StandardCopyOption.REPLACE_EXISTING);
- CxLogger.info(LOG_TAG + " Copied companion file: " + companionFileName);
- } catch (IOException e) {
- CxLogger.warning(LOG_TAG + " Error occurred while saving companion file: " + e.getMessage());
- }
- }
-
- /**
- * Infers companion lock file name based on manifest file name.
- */
- private String getCompanionFileName(String fileName) {
- if ("package.json".equalsIgnoreCase(fileName)) {
- return "package-lock.json";
- }
- if (fileName.toLowerCase().endsWith(".csproj")) {
- return "package.lock.json";
+ Path companionTempPath = tempFolderPath.resolve(companionFileName);
+ try {
+ Files.copy(companionOriginalPath, companionTempPath, StandardCopyOption.REPLACE_EXISTING);
+ CxLogger.info(LOG_TAG + " Copied companion file: " + companionFileName);
+ } catch (IOException e) {
+ CxLogger.warning(LOG_TAG + " Error occurred while saving companion file: " + e.getMessage());
+ }
}
- return "";
}
/**
@@ -299,12 +300,4 @@ private String getFileContent(String filePath, IDocument document) {
}
return null;
}
-
-// private String getIgnoreFilePath(IProject proj) {
-// try {
-// return DevAssistUtils.getIgnoreFilePath(proj);
-// } catch (Exception e) {
-// return "";
-// }
-// }
}
\ No newline at end of file
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/secrets/SecretsScannerService.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/secrets/SecretsScannerService.java
index 1fba92d4..16886279 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/secrets/SecretsScannerService.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/scanners/secrets/SecretsScannerService.java
@@ -1,30 +1,35 @@
package com.checkmarx.eclipse.devassist.scanners.secrets;
-import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults;
-import com.checkmarx.ast.wrapper.CxException;
-import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
-import com.checkmarx.eclipse.devassist.common.ScanResult;
-import com.checkmarx.eclipse.devassist.common.ScannerConfig;
-import com.checkmarx.eclipse.devassist.factory.CxWrapperFactory;
-import com.checkmarx.eclipse.devassist.model.ScanIssue;
-import com.checkmarx.eclipse.devassist.model.ScanEngine;
-import com.checkmarx.eclipse.devassist.utils.DevAssistUtils;
-import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
-import com.checkmarx.eclipse.common.utils.CxLogger;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.jface.text.Document;
-import org.eclipse.jface.text.IDocument;
-
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.nio.file.*;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalTime;
-import java.util.*;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
import java.util.stream.Collectors;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+
+import com.checkmarx.ast.secretsrealtime.SecretsRealtimeResults;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.wrapper.WrapperProvider;
+import com.checkmarx.eclipse.devassist.basescanner.BaseScannerService;
+import com.checkmarx.eclipse.devassist.common.ScanResult;
+import com.checkmarx.eclipse.devassist.common.ScannerConfig;
+import com.checkmarx.eclipse.devassist.model.ScanEngine;
+import com.checkmarx.eclipse.devassist.utils.DevAssistConstants;
+
/**
* Realtime Secrets scanner service for Eclipse.
*
@@ -37,6 +42,7 @@ public class SecretsScannerService extends BaseScannerService MANIFEST_FILE_PATTERNS = List.of(
@@ -127,7 +133,7 @@ public ScanResult scan(String filePath, IDocument docume
CxLogger.info(LOG_TAG + " Starting scan: " + filePath);
// String ignoreFilePath = getIgnoreFilePath(proj);
- SecretsRealtimeResults scanResults = CxWrapperFactory.build()
+ SecretsRealtimeResults scanResults = wrapperProvider
.secretsRealtimeScan(tempFilePath.get(), "");
if (scanResults == null) {
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/ui/findings/CxFindingsView.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/ui/findings/CxFindingsView.java
index 80834dfc..60eb0762 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/ui/findings/CxFindingsView.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/ui/findings/CxFindingsView.java
@@ -34,7 +34,6 @@
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.ui.plugin.AbstractUIPlugin;
-import org.apache.commons.lang3.StringUtils;
import com.checkmarx.eclipse.devassist.ui.findings.provider.FindingsContentProvider;
import com.checkmarx.eclipse.devassist.ui.findings.provider.FindingsLabelProvider;
@@ -143,7 +142,7 @@ private void refreshViewMode() {
return;
}
- if (StringUtils.isBlank(Preferences.getApiKey())) {
+ if (!Preferences.isAuthenticated()) {
drawMissingCredentialsPanel(parentComposite);
} else {
loadCachedIssues();
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/DevAssistConstants.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/DevAssistConstants.java
index 74950313..dab7236b 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/DevAssistConstants.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/DevAssistConstants.java
@@ -73,38 +73,48 @@ private DevAssistConstants() {
// Manifest file patterns
public static final List MANIFEST_FILE_PATTERNS = List.of(
- "**/Directory.Packages.props",
- "**/packages.config",
- "**/pom.xml",
- "**/package.json",
- "**/requirements.txt",
- "**/go.mod",
- "**/*.csproj",
- "**/build.gradle",
- "**/build.gradle.kts",
- "**/yarn.lock",
- "**/*.sbt",
- "**/Gemfile",
- "**/bower.json",
- "**/requirement-*.txt",
- "**/requirements-*.txt",
- "**/Setup.py",
- "**/Setup.cfg",
- "**/pyproject.toml",
- "**/poetry.lock",
- "**/Package.swift",
- "**/Package.resolved",
- "**/composer.json",
- "**/composer.lock",
- "**/*.podspec.json",
- "**/*.podspec",
- "**/Podfile",
- "**/Podfile.lock",
- "**/Cartfile.resolved",
- "**/Gemfile.lock",
- "**/cpanfile.snapshot",
- "**/cpanfile",
- "**/pubspec.lock");
+ // .NET
+ "**/Directory.Packages.props",
+ "**/packages.config",
+ "**/*.csproj",
+ // Maven
+ "**/pom.xml",
+ // npm
+ "**/package.json",
+ // Bower
+ "**/bower.json",
+ // Python
+ "**/requirement*.txt",
+ "**/constraints.txt",
+ "**/constraints-*.txt",
+ "**/pyproject.toml",
+ "**/setup.cfg",
+ "**/setup.py",
+ // Go
+ "**/go.mod",
+ // Gradle
+ "**/*.gradle",
+ "**/*.gradle.kts",
+ "**/libs.versions.toml",
+ // SBT
+ "**/*.sbt",
+ // iOS CocoaPods
+ "**/Podfile",
+ "**/*.podspec",
+ "**/*.podspec.json",
+ // iOS Carthage
+ "**/Cartfile",
+ "**/Cartfile.private",
+ // Swift Package Manager
+ "**/Package.swift",
+ "**/Package@swift-*.swift",
+ // Dart/Flutter
+ "**/pubspec.yaml",
+ // Ruby
+ "**/Gemfile",
+ // PHP Composer
+ "**/composer.json"
+ );
// Container file patterns
public static final List CONTAINERS_FILE_PATTERNS = List.of(
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/DevAssistUtils.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/DevAssistUtils.java
index d8d48717..08df5ee5 100644
--- a/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/DevAssistUtils.java
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/DevAssistUtils.java
@@ -1,15 +1,21 @@
package com.checkmarx.eclipse.devassist.utils;
+import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Base64;
-import java.util.List;
import java.util.Objects;
-import java.net.URL;
+
import org.eclipse.core.resources.IFile;
+import org.eclipse.e4.ui.css.swt.theme.ITheme;
+import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
@@ -18,20 +24,13 @@
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.Clipboard;
-import org.eclipse.swt.dnd.TextTransfer;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.e4.ui.css.swt.theme.ITheme;
-import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
-import org.eclipse.swt.dnd.Clipboard;
-import org.eclipse.swt.dnd.TextTransfer;
+import com.checkmarx.eclipse.common.preferences.Preferences;
+import com.checkmarx.eclipse.common.utils.CxLogger;
import com.checkmarx.eclipse.devassist.backend.SeverityLevel;
import com.checkmarx.eclipse.devassist.model.ScanIssue;
import com.checkmarx.eclipse.devassist.model.Vulnerability;
import com.checkmarx.eclipse.devassist.remediation.NotificationPopup;
-import com.checkmarx.eclipse.common.utils.CxLogger;
/**
* Utility class for DevAssist operations. Provides methods for encoding,
@@ -437,4 +436,25 @@ private static boolean isDarkByBackgroundLuminance() {
/ 255.0;
return luminance < 0.5;
}
+
+ /**
+ * Returns the container tool configured in the global settings.
+ * @return
+ */
+ public static String getContainerTool() {
+ try {
+ // Prefer the typed preference store access which returns the stored value
+ // or an empty string if not present. Fall back to the generic getPref
+ // only if needed. Always return a sensible default when empty/null.
+ String value = Preferences.STORE.getString(Preferences.PREF_CONTAINERS_TOOL);
+ if (value == null || value.isBlank()) {
+ // Try the legacy getter which may consult the preference service
+ value = Preferences.getPref(Preferences.PREF_CONTAINERS_TOOL);
+ }
+ return (value == null || value.isBlank()) ? "docker" : value;
+ } catch (Exception e) {
+ CxLogger.error(LOG_TAG + " Error retrieving container tool preference: " + e.getMessage(), e);
+ return "docker"; // default to docker if preference retrieval fails
+ }
+ }
}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/PackageManager.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/PackageManager.java
new file mode 100644
index 00000000..727d54f9
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/utils/PackageManager.java
@@ -0,0 +1,247 @@
+package com.checkmarx.eclipse.devassist.utils;
+
+import java.util.List;
+
+import com.checkmarx.eclipse.common.utils.CxLogger;
+
+/**
+ * The PackageManager enum represents various package managers used in software development.
+ * Each constant corresponds to a specific package manager, and the enum provides utility methods for mapping and checking supported package managers.
+ */
+public enum PackageManager {
+
+ DOTNET("dotnet", ManifestFilePattern.DOTNET),
+ GRADLE("gradle", ManifestFilePattern.GRADLE),
+ MAVEN("mvn", ManifestFilePattern.MAVEN),
+ SBT("sbt", ManifestFilePattern.SBT),
+ NPM("npm", ManifestFilePattern.NPM),
+ GO("go", ManifestFilePattern.GO),
+ PYTHON("python", ManifestFilePattern.PYTHON),
+ BOWER("bower", ManifestFilePattern.BOWER),
+ //YARN("yarn", ManifestFilePattern.YARN),
+ //COCOAPODS("cocoapods", ManifestFilePattern.COCOAPODS),
+ //CARTHAGE("carthage", ManifestFilePattern.CARTHAGE),
+ //SWIFT("swift", ManifestFilePattern.SWIFT),
+ //DART("dart", ManifestFilePattern.DART),
+ RUBY("ruby", ManifestFilePattern.RUBY),
+ PHP("php", ManifestFilePattern.PHP),
+ UNKNOWN("unknown", null);
+
+ private String packageManager;
+ private ManifestFilePattern manifestPattern;
+
+ PackageManager(String packageManager, ManifestFilePattern pattern) {
+ this.packageManager = packageManager;
+ this.manifestPattern = pattern;
+ }
+
+ public String getPackageManager() {
+ return packageManager;
+ }
+
+ public ManifestFilePattern getManifestPattern() {
+ return manifestPattern;
+ }
+
+ /**
+ * Maps a string representation of a package manager to its corresponding PackageManager enum constant.
+ * @param packageManager
+ * @return
+ */
+ public static PackageManager fromString(String packageManager) {
+ for (PackageManager pm : PackageManager.values()) {
+ if (pm.packageManager.equalsIgnoreCase(packageManager)) {
+ return pm;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ /**
+ * Checks if a given string representation of a package manager is supported by the system.
+ * @param packageManager
+ * @return
+ */
+ public static boolean isSupportedPackageManager(String packageManager) {
+ for (PackageManager pm : PackageManager.values()) {
+ if (pm.packageManager.equalsIgnoreCase(packageManager)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Maps a given package manager to its corresponding remediation format.
+ * For example, Gradle and SBT are mapped to Maven, while CocoaPods and Carthage are mapped to Swift.
+ * If the package manager is not recognized or is null/empty, it returns the original input.
+ *
+ * @param packageManager The string representation of the package manager to be mapped.
+ * @return The corresponding remediation format for the given package manager.
+ */
+ public static String mapToRemediationFormat(String packageManager) {
+ if (packageManager == null || packageManager.isEmpty()) {
+ CxLogger.warning("[PACKAGE-MANAGER] Package manager is null or empty, returning as is.");
+ return packageManager;
+ }
+ PackageManager pm = fromString(packageManager.toLowerCase());
+
+ switch (pm) {
+ case GRADLE:
+ case SBT:
+ return MAVEN.getPackageManager();
+ //case COCOAPODS:
+ //case CARTHAGE:
+ //return SWIFT.getPackageManager();
+ default:
+ return packageManager;
+ }
+ }
+
+
+ /**
+ * Infers companion lock file names based on the manifest file name.
+ * Some manifests may have multiple companion files (e.g., package.json has both package-lock.json and yarn.lock).
+ *
+ * @param fileName name of the manifest file
+ * @return list of companion file names; empty list if no companions are defined
+ */
+ public static List getCompanionFileNames(String fileName) {
+ // npm/Yarn - support both package-lock.json (npm) and yarn.lock (yarn)
+ if (fileName.equals("package.json")) {
+ return List.of(CompanionFileType.PACKAGE_LOCK_JSON.getCompFileName(), CompanionFileType.YARN_LOCK.getCompFileName());
+ }
+
+ // .NET
+ if (fileName.contains(".csproj")) {
+ return getCompanionFileNamesByType(CompanionFileType.PACKAGES_LOCK_JSON);
+ }
+
+ // Swift Package Manager (AST-165765)
+ if (fileName.equals("Package.swift")) {
+ return getCompanionFileNamesByType(CompanionFileType.PACKAGE_RESOLVED);
+ }
+ if (fileName.startsWith("Package@swift-") && fileName.endsWith(".swift")) {
+ return List.of(fileName.replace(".swift", ".resolved"));
+ }
+
+ // CocoaPods (AST-165761)
+ if (fileName.equals("Podfile")) {
+ return getCompanionFileNamesByType(CompanionFileType.PODFILE_LOCK);
+ }
+
+ // Carthage
+ if (fileName.equals("Cartfile") || fileName.equals("Cartfile.private")) {
+ return getCompanionFileNamesByType(CompanionFileType.CARTFILE_RESOLVED);
+ }
+
+ // Ruby Bundler
+ if (fileName.equals("Gemfile")) {
+ return getCompanionFileNamesByType(CompanionFileType.GEMFILE_LOCK);
+ }
+
+ // PHP Composer
+ if (fileName.equals("composer.json")) {
+ return getCompanionFileNamesByType(CompanionFileType.COMPOSER_LOCK);
+ }
+
+ // Python Poetry
+ if (fileName.equals("pyproject.toml")) {
+ return getCompanionFileNamesByType(CompanionFileType.POETRY_LOCK);
+ }
+
+ // Dart/Flutter Pub
+ if (fileName.equals("pubspec.yaml")) {
+ return getCompanionFileNamesByType(CompanionFileType.PUBSPEC_LOCK);
+ }
+ return List.of();
+ }
+
+ /**
+ * Enum representing companion lock file types for various package managers.
+ */
+ static enum CompanionFileType {
+
+ PACKAGE_LOCK_JSON("package-lock.json"),
+ YARN_LOCK("yarn.lock"),
+ PACKAGES_LOCK_JSON("packages.lock.json"),
+ PACKAGE_RESOLVED("Package.resolved"),
+ PODFILE_LOCK("Podfile.lock"),
+ CARTFILE_RESOLVED("Cartfile.resolved"),
+ GEMFILE_LOCK("Gemfile.lock"),
+ COMPOSER_LOCK("composer.lock"),
+ POETRY_LOCK("poetry.lock"),
+ PUBSPEC_LOCK("pubspec.lock");
+
+ private String compFileName;
+
+ CompanionFileType(String compFileName) {
+ this.compFileName = compFileName;
+ }
+
+ public String getCompFileName() {
+ return compFileName;
+ }
+ }
+
+ public static List getCompanionFileNamesByType(CompanionFileType type) {
+ if (type == null) {
+ return List.of();
+ }
+ return List.of(type.getCompFileName());
+ }
+
+ /**
+ * Enum representing manifest file patterns for various package managers.
+ * Each constant corresponds to a specific package manager and holds a list of file patterns used to identify manifest files.
+ */
+ static enum ManifestFilePattern {
+
+ DOTNET(List.of("**/Directory.Packages.props", "**/packages.config","**/*.csproj")),
+ GRADLE(List.of("**/*.gradle", "**/*.gradle.kts", "**/libs.versions.toml")),
+ MAVEN(List.of("**/pom.xml")),
+ SBT(List.of("**/*.sbt")),
+ NPM(List.of("**/package.json")),
+ GO(List.of("**/go.mod")),
+ PYTHON(List.of("**/requirement*.txt", "**/constraints.txt", "**/constraints-*.txt", "**/pyproject.toml",
+ "**/setup.cfg", "**/setup.py")),
+ BOWER(List.of("**/bower.json")),
+ //YARN(List.of("package.json", "yarn.lock")),
+ //COCOAPODS(List.of("**/Podfile", "**/*.podspec", "**/*.podspec.json")),
+ //CARTHAGE(List.of("**/Cartfile", "**/Cartfile.private")),
+ //SWIFT(List.of("**/Package.swift", "**/Package@swift-*.swift")),
+ //DART(List.of("**/pubspec.yaml")),
+ RUBY(List.of("**/Gemfile")),
+ PHP(List.of("**/composer.json"));
+
+ private List patterns;
+
+ ManifestFilePattern(List filePatterns) {
+ this.patterns = filePatterns;
+ }
+
+ public List getPatterns() {
+ return patterns;
+ }
+ }
+
+ /**
+ * Retrieves all defined manifest file patterns across all package managers.
+ * If a package manager has no defined patterns, a warning is logged.
+ *
+ * @return a list of all manifest file patterns
+ */
+ public static List getAllPatterns() {
+
+ List allPatterns = new java.util.ArrayList<>();
+
+ for (ManifestFilePattern pattern : ManifestFilePattern.values()) {
+ if (pattern.getPatterns() == null || pattern.getPatterns().isEmpty()) {
+ CxLogger.warning("[PACKAGE-MANAGER] ManifestFilePattern " + pattern.name() + " has no defined patterns.");
+ }
+ allPatterns.addAll(pattern.getPatterns());
+ }
+ return allPatterns;
+ }
+
+}