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/listener/IMcpUninstallCallback.java b/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpUninstallCallback.java
new file mode 100644
index 00000000..a07cdba7
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpUninstallCallback.java
@@ -0,0 +1,29 @@
+package com.checkmarx.eclipse.common.listener;
+
+/**
+ * Receives the outcome of an MCP uninstall triggered after logout.
+ *
+ *
May be invoked from a background thread - implementations that touch SWT
+ * widgets must marshal onto the display thread themselves.
+ */
+public interface IMcpUninstallCallback {
+
+ /**
+ * Called when the MCP configuration was uninstalled/removed successfully.
+ */
+ void onSuccess();
+
+ /**
+ * Called when no MCP configuration entry was found to uninstall - the uninstall
+ * operation succeeded, but there was nothing to remove (already uninstalled,
+ * or never installed in the first place).
+ */
+ void onNotFound();
+
+ /**
+ * Called when the uninstall 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/IMcpUninstallHandler.java b/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpUninstallHandler.java
new file mode 100644
index 00000000..503f00a4
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/listener/IMcpUninstallHandler.java
@@ -0,0 +1,18 @@
+package com.checkmarx.eclipse.common.listener;
+
+/**
+ * Service for uninstalling the Checkmarx MCP server configuration.
+ *
+ * Allows preference pages in common-lib (e.g. PreferencesPage) to trigger
+ * MCP uninstallation on logout without depending on devassist-lib, which owns the actual
+ * McpInstallService implementation.
+ */
+public interface IMcpUninstallHandler {
+
+ /**
+ * Uninstalls/removes the Checkmarx MCP server configuration after logout.
+ *
+ * @param callback notified of success or failure, possibly from a background thread
+ */
+ void uninstallMcp(IMcpUninstallCallback callback);
+}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/listener/IProjectLifecycleListener.java b/common-lib/src/com/checkmarx/eclipse/common/listener/IProjectLifecycleListener.java
new file mode 100644
index 00000000..5f0a82f8
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/listener/IProjectLifecycleListener.java
@@ -0,0 +1,33 @@
+package com.checkmarx.eclipse.common.listener;
+
+/**
+ * Interface for handling project lifecycle and post-authentication scanning.
+ *
+ * Implemented by DevAssist module to trigger workspace scans after successful authentication.
+ */
+public interface IProjectLifecycleListener {
+
+ /**
+ * Register this listener with Eclipse workspace.
+ * Must be called during plugin initialization to activate project lifecycle monitoring.
+ */
+ void register();
+
+ /**
+ * Initiates scans for all projects already open in the workspace.
+ * Called after successful user authentication to ensure all open projects
+ * are scanned with the newly authenticated credentials.
+ */
+ void scanAlreadyOpenProjects();
+
+ /**
+ * Re-runs the workspace file scan (manifest/IaC/container patterns) for every
+ * open project, regardless of whether it was already initialized.
+ *
+ * Unlike {@link #scanAlreadyOpenProjects()}, which only initializes projects
+ * that haven't been set up yet, this forces a fresh scan of already-initialized
+ * projects too. Used when scanner preferences change (e.g. a scanner is enabled)
+ * and previously-scanned projects need to be rescanned with the new scanner set.
+ */
+ void rescanAllOpenProjects();
+}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/listener/ISettingsChangeNotifier.java b/common-lib/src/com/checkmarx/eclipse/common/listener/ISettingsChangeNotifier.java
new file mode 100644
index 00000000..5659bbe0
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/listener/ISettingsChangeNotifier.java
@@ -0,0 +1,16 @@
+package com.checkmarx.eclipse.common.listener;
+
+/**
+ * Notifies listeners when settings have been applied or changed.
+ *
+ * Allows PreferencesPage (common-lib) to notify the main plugin about settings
+ * changes without creating a reverse dependency.
+ */
+public interface ISettingsChangeNotifier {
+
+ /**
+ * Notify that settings have been applied/changed.
+ * This triggers UI updates in views and components.
+ */
+ void notifySettingsApplied();
+}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/listener/IWorkspaceScanService.java b/common-lib/src/com/checkmarx/eclipse/common/listener/IWorkspaceScanService.java
new file mode 100644
index 00000000..7aeaa6cf
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/listener/IWorkspaceScanService.java
@@ -0,0 +1,17 @@
+package com.checkmarx.eclipse.common.listener;
+
+/**
+ * Service for triggering workspace scans after authentication.
+ *
+ * Allows AuthenticationSuccessHandler (devassist-lib) to trigger workspace scans
+ * without importing ProjectLifecycleListener or PluginStartup from main plugin.
+ */
+public interface IWorkspaceScanService {
+
+ /**
+ * Scan all open projects in the workspace.
+ * Called after successful authentication to ensure all open projects
+ * are scanned with the newly authenticated credentials.
+ */
+ void scanWorkspace();
+}
diff --git a/checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/properties/AuthButtonFieldEditor.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/AuthButtonFieldEditor.java
similarity index 88%
rename from checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/properties/AuthButtonFieldEditor.java
rename to common-lib/src/com/checkmarx/eclipse/common/preferences/AuthButtonFieldEditor.java
index bd13259d..66da9dc6 100644
--- a/checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/properties/AuthButtonFieldEditor.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/AuthButtonFieldEditor.java
@@ -1,4 +1,4 @@
-package com.checkmarx.eclipse.properties;
+package com.checkmarx.eclipse.common.preferences;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -9,9 +9,9 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
-import com.checkmarx.eclipse.runner.Authenticator;
-import com.checkmarx.eclipse.utils.CxLogger;
-import com.checkmarx.eclipse.utils.PluginConstants;
+import com.checkmarx.eclipse.common.runner.Authenticator;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.common.utils.PluginConstants;
public class AuthButtonFieldEditor extends StringButtonFieldEditor {
diff --git a/common-lib/src/com/checkmarx/eclipse/common/preferences/CheckmarxPreferencePage.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/CheckmarxPreferencePage.java
new file mode 100644
index 00000000..7440bb3d
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/CheckmarxPreferencePage.java
@@ -0,0 +1,473 @@
+package com.checkmarx.eclipse.common.preferences;
+
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.*;
+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;
+
+/**
+ * Preference page for configuring Checkmarx scanner settings.
+ * Allows users to enable/disable individual scanners and select scan frequency.
+ */
+public class CheckmarxPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
+
+ // Preference Keys
+ public static final String PREF_ASCA_ENABLED = "scanner.asca.enabled";
+ public static final String PREF_OSS_ENABLED = "scanner.oss.enabled";
+ public static final String PREF_SECRETS_ENABLED = "scanner.secrets.enabled";
+ public static final String PREF_CONTAINERS_ENABLED = "scanner.containers.enabled";
+ public static final String PREF_IAC_ENABLED = "scanner.iac.enabled";
+ public static final String PREF_CONTAINERS_TOOL = "scanner.containers.tool";
+
+ // Controls
+ private Label assistMessageLabel;
+ private Button ascaCheckbox;
+ private Button ossCheckbox;
+ private Button secretsCheckbox;
+ private Button containersCheckbox;
+ private Button iacCheckbox;
+ private Combo containersToolCombo;
+ private Label mcpStatusLabel;
+ private boolean loggedIn;
+
+ public CheckmarxPreferencePage() {
+ super();
+ setPreferenceStore(com.checkmarx.eclipse.common.preferences.Preferences.STORE);
+ // Listen for preference changes to update login state.
+ // Critical: if user logs out in another page while this page is visible in the
+ // same
+ // dialog session, we need to refresh the UI to show logged-out content instead
+ // of stale
+ // logged-in checkboxes. Without this, performOk() would still run with stale
+ // loggedIn=true.
+ Preferences.STORE.addPropertyChangeListener(this::handlePreferenceChange);
+ }
+
+ /**
+ * Called when preferences change (e.g., user logs out in another page of the
+ * same dialog).
+ * Re-reads the login state and updates the visible UI accordingly.
+ */
+ 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 = Preferences.isAuthenticated();
+ if (loggedIn != isNowLoggedIn) {
+ loggedIn = isNowLoggedIn;
+ }
+ }
+
+ @Override
+ protected Control createContents(Composite parent) {
+ loggedIn = Preferences.isAuthenticated();
+ if (!loggedIn) {
+ return createLoggedOutContent(parent);
+ }
+
+ Composite mainPanel = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout(1, false);
+ layout.verticalSpacing = 8;
+ layout.horizontalSpacing = 0;
+ mainPanel.setLayout(layout);
+ mainPanel.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ // Assist Message Label (Hidden by default, red text)
+ assistMessageLabel = new Label(mainPanel, SWT.NONE);
+ assistMessageLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));
+ GridData msgData = new GridData(GridData.FILL_HORIZONTAL);
+ msgData.exclude = true; // Equivalent to hidemode 3
+ assistMessageLabel.setLayoutData(msgData);
+ assistMessageLabel.setVisible(false);
+
+ // --- ASCA Section ---
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_ASCA_TITLE);
+ Composite ascaComp = createIndentComposite(mainPanel);
+ ascaCheckbox = new Button(ascaComp, SWT.CHECK);
+ ascaCheckbox.setText(PluginConstants.ASCA_CHECKBOX);
+
+ // --- OSS Section ---
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_OSS_TITLE);
+ Composite ossComp = createIndentComposite(mainPanel);
+ ossCheckbox = new Button(ossComp, SWT.CHECK);
+ ossCheckbox.setText(PluginConstants.OSS_REALTIME_CHECKBOX);
+
+ // --- Secrets Section ---
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_SECRETS_TITLE);
+ Composite secretsComp = createIndentComposite(mainPanel);
+ secretsCheckbox = new Button(secretsComp, SWT.CHECK);
+ secretsCheckbox.setText(PluginConstants.SECRETS_REALTIME_CHECKBOX);
+
+ // --- Containers Section ---
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_CONTAINERS_TITLE);
+ Composite containersComp = createIndentComposite(mainPanel);
+ containersCheckbox = new Button(containersComp, SWT.CHECK);
+ containersCheckbox.setText(PluginConstants.CONTAINERS_REALTIME_CHECKBOX);
+
+ // --- IaC Section ---
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_TITLE);
+ Composite iacComp = createIndentComposite(mainPanel);
+ iacCheckbox = new Button(iacComp, SWT.CHECK);
+ iacCheckbox.setText(PluginConstants.IAC_REALTIME_CHECKBOX);
+
+ // --- Container Tool Selection Section ---
+ createSectionHeader(mainPanel, PluginConstants.DEVASSIST_PLUGIN_REALTIME_SCANNERS_IAC_PREFIX);
+ Composite containerToolComp = createIndentComposite(mainPanel);
+ Label containerDesc = new Label(containerToolComp, SWT.WRAP);
+ 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(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
+ * One".
+ */
+ private Control createLoggedOutContent(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout(1, false);
+ layout.marginTop = 20;
+ composite.setLayout(layout);
+ composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ Label message = new Label(composite, SWT.WRAP);
+ message.setText(PluginConstants.LOGIN_NOTE_CXONE_ASSIST);
+ message.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ Link goToLoginLink = new Link(composite, SWT.NONE);
+ goToLoginLink.setText(""+PluginConstants.GO_TO_CHECKMARX_ONE+"");
+ goToLoginLink.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
+ goToLoginLink.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(
+ parent.getShell(), "com.checkmarx.eclipse.properties.preferencespage", null, null);
+ if (dialog != null) {
+ dialog.open();
+ }
+ }
+ });
+
+ return composite;
+ }
+
+ private Composite createIndentComposite(Composite parent) {
+ Composite comp = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout(1, false);
+ layout.marginLeft = 15;
+ layout.marginTop = 0;
+ comp.setLayout(layout);
+ comp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ return comp;
+ }
+
+ private void loadValues() {
+ IPreferenceStore store = getPreferenceStore();
+ ascaCheckbox.setSelection(store.getBoolean(PREF_ASCA_ENABLED));
+ ossCheckbox.setSelection(store.getBoolean(PREF_OSS_ENABLED));
+ secretsCheckbox.setSelection(store.getBoolean(PREF_SECRETS_ENABLED));
+ containersCheckbox.setSelection(store.getBoolean(PREF_CONTAINERS_ENABLED));
+ iacCheckbox.setSelection(store.getBoolean(PREF_IAC_ENABLED));
+
+ String tool = store.getString(PREF_CONTAINERS_TOOL);
+ if (tool != null && !tool.isBlank()) {
+ containersToolCombo.setText(tool);
+ } else if (containersToolCombo.getItemCount() > 0) {
+ containersToolCombo.select(0);
+ }
+ }
+
+ @Override
+ protected void performDefaults() {
+ // Check credentials fresh, not from captured field.
+ // If user logged out while viewing another page, loggedIn would be stale.
+ boolean isCurrentlyLoggedIn = Preferences.isAuthenticated();
+ if (!isCurrentlyLoggedIn) {
+ super.performDefaults();
+ return;
+ }
+ IPreferenceStore store = getPreferenceStore();
+ ascaCheckbox.setSelection(store.getDefaultBoolean(PREF_ASCA_ENABLED));
+ ossCheckbox.setSelection(store.getDefaultBoolean(PREF_OSS_ENABLED));
+ secretsCheckbox.setSelection(store.getDefaultBoolean(PREF_SECRETS_ENABLED));
+ containersCheckbox.setSelection(store.getDefaultBoolean(PREF_CONTAINERS_ENABLED));
+ iacCheckbox.setSelection(store.getDefaultBoolean(PREF_IAC_ENABLED));
+ super.performDefaults();
+ }
+
+ /**
+ * Helper to create a titled section with a horizontal line separator.
+ */
+ private void createSectionHeader(Composite parent, String titleText) {
+ Composite headerComp = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout(2, false);
+ layout.marginWidth = 0;
+ layout.marginTop = 6;
+ layout.marginBottom = 0;
+ headerComp.setLayout(layout);
+ headerComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ int colonIndex = titleText.indexOf(":");
+
+ StyledText title = new StyledText(headerComp, SWT.READ_ONLY | SWT.WRAP);
+ title.setText(titleText);
+ title.setBackground(headerComp.getBackground()); // Match background color
+ title.setCaret(null); // Hide text cursor
+
+ if (colonIndex != -1 && colonIndex + 1 < titleText.length()) {
+ int start = colonIndex + 1; // Start right after the colon
+ int length = titleText.length() - start;
+
+ StyleRange boldStyle = new StyleRange();
+ boldStyle.start = start;
+ boldStyle.length = length;
+ boldStyle.fontStyle = SWT.BOLD;
+
+ title.setStyleRange(boldStyle);
+
+ }
+ }
+
+ @Override
+ public void init(IWorkbench workbench) {
+ // Initialization if needed
+ }
+
+ @Override
+ public boolean performOk() {
+ // Check credentials fresh, not from captured field.
+ // Critical: if user logged out while viewing another page within the same
+ // dialog session,
+ // loggedIn would be stale and we'd save/notify with false authentication
+ // status.
+ boolean isCurrentlyLoggedIn = Preferences.isAuthenticated();
+ if (!isCurrentlyLoggedIn) {
+ return super.performOk();
+ }
+ IPreferenceStore store = getPreferenceStore();
+
+ // Get current UI selections. It's possible this page's controls were never
+ // created (Eclipse may call performOk() on pages that haven't had
+ // createContents() invoked), or the controls may have been disposed. In
+ // that case fall back to the stored preference values instead of
+ // dereferencing null controls which caused an NPE in the field.
+ boolean ascaSelected = store.getBoolean(PREF_ASCA_ENABLED);
+ if (ascaCheckbox != null && !ascaCheckbox.isDisposed()) {
+ ascaSelected = ascaCheckbox.getSelection();
+ }
+
+ boolean ossSelected = store.getBoolean(PREF_OSS_ENABLED);
+ if (ossCheckbox != null && !ossCheckbox.isDisposed()) {
+ ossSelected = ossCheckbox.getSelection();
+ }
+
+ boolean secretsSelected = store.getBoolean(PREF_SECRETS_ENABLED);
+ if (secretsCheckbox != null && !secretsCheckbox.isDisposed()) {
+ secretsSelected = secretsCheckbox.getSelection();
+ }
+
+ boolean containersSelected = store.getBoolean(PREF_CONTAINERS_ENABLED);
+ if (containersCheckbox != null && !containersCheckbox.isDisposed()) {
+ containersSelected = containersCheckbox.getSelection();
+ }
+
+ boolean iacSelected = store.getBoolean(PREF_IAC_ENABLED);
+ if (iacCheckbox != null && !iacCheckbox.isDisposed()) {
+ iacSelected = iacCheckbox.getSelection();
+ }
+
+ String containersTool = store.getString(PREF_CONTAINERS_TOOL);
+ if (containersToolCombo != null && !containersToolCombo.isDisposed()) {
+ try {
+ String text = containersToolCombo.getText();
+ if (text != null && !text.isBlank()) {
+ containersTool = text;
+ }
+ } catch (Exception ex) {
+ // Defensive: protect against any SWT oddities; fall back to store value
+ CxLogger.warning("[PREFS-PAGE] Failed to read containersToolCombo text, using stored value: " + ex.getMessage());
+ }
+ }
+
+ // Step 1: Save current UI state to preference store
+ store.setValue(PREF_ASCA_ENABLED, ascaSelected);
+ store.setValue(PREF_OSS_ENABLED, ossSelected);
+ store.setValue(PREF_SECRETS_ENABLED, secretsSelected);
+ store.setValue(PREF_CONTAINERS_ENABLED, containersSelected);
+ store.setValue(PREF_IAC_ENABLED, iacSelected);
+ if (containersTool != null) {
+ store.setValue(PREF_CONTAINERS_TOOL, containersTool);
+ }
+
+ // Diagnostic: Verify what was saved
+ CxLogger.info("[PREFS-PAGE] Saved to preference store: ASCA=" + ascaSelected + ", OSS=" + ossSelected +
+ ", SECRETS=" + secretsSelected + ", CONTAINERS=" + containersSelected + ", IAC=" + iacSelected);
+
+ // Step 2: Save as user preferences (mirrors JetBrains apply() method)
+ // This preserves user's choices if features toggle on/off later
+ Preferences.setUserPreferences(ascaSelected, ossSelected, secretsSelected,
+ containersSelected, iacSelected);
+ CxLogger.info("[PREFS-PAGE] Saved as user preferences");
+
+ // Step 3: Notify listeners (e.g., GlobalScannerController) about preference
+ // changes
+ // The listener will update GlobalScannerController based on new preferences
+ // This decouples CheckmarxPreferencePage from devassist-lib modules
+ for (ISettingsChangeNotifier notifier : Preferences.getSettingsChangeNotifiers()) {
+ try {
+ notifier.notifySettingsApplied();
+ CxLogger.info("[PREFS] Notified settings change listeners");
+ } catch (Exception e) {
+ CxLogger.warning("[PREFS] Failed to notify settings change: " + e.getMessage());
+ }
+ }
+
+ // Step 4: Trigger change event for listeners
+ store.firePropertyChangeEvent("scannerPreferencesChanged", null, null);
+
+ return super.performOk();
+ }
+
+}
diff --git a/common-lib/src/com/checkmarx/eclipse/common/preferences/CxPreferencesDialogSizing.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/CxPreferencesDialogSizing.java
new file mode 100644
index 00000000..97d1401c
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/CxPreferencesDialogSizing.java
@@ -0,0 +1,48 @@
+package com.checkmarx.eclipse.common.preferences;
+
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Shell;
+
+
+/**
+ * Eclipse's shared Window > Preferences dialog (WorkbenchPreferenceDialog) remembers
+ * its shell size across sessions. Once that remembered size is smaller than what this
+ * plugin's own pages need, they get clipped behind an inner scrollbar on every later
+ * reopen, no matter how much content they actually have.
+ *
+ * Rather than changing that shared dialog's sizing/resizing behaviour - which would
+ * also affect every other plugin's preference pages - this only grows the dialog
+ * (never shrinks it) while one of this plugin's own pages is the one actually being
+ * shown, right when it's first shown and again on every later switch back to it.
+ */
+public final class CxPreferencesDialogSizing {
+
+ private CxPreferencesDialogSizing() {
+ }
+
+ public static void applyTo(PreferenceDialog dialog) {
+ growIfOwnPage(dialog, dialog.getSelectedPage());
+ dialog.addPageChangedListener(event -> growIfOwnPage(dialog, event.getSelectedPage()));
+ }
+
+ private static void growIfOwnPage(PreferenceDialog dialog, Object page) {
+ if (!(page instanceof PreferencesPage) && !(page instanceof CheckmarxPreferencePage)) {
+ return;
+ }
+
+ Shell shell = dialog.getShell();
+ if (shell == null || shell.isDisposed()) {
+ return;
+ }
+
+ Point required = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
+ Point current = shell.getSize();
+ int width = Math.max(required.x, current.x);
+ int height = Math.max(required.y, current.y);
+ if (width != current.x || height != current.y) {
+ shell.setSize(width, height);
+ }
+ }
+}
diff --git a/checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/properties/LabelFieldEditor.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/LabelFieldEditor.java
similarity index 96%
rename from checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/properties/LabelFieldEditor.java
rename to common-lib/src/com/checkmarx/eclipse/common/preferences/LabelFieldEditor.java
index 7330a99d..713ce9fe 100644
--- a/checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/properties/LabelFieldEditor.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/LabelFieldEditor.java
@@ -1,4 +1,4 @@
-package com.checkmarx.eclipse.properties;
+package com.checkmarx.eclipse.common.preferences;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.swt.layout.GridData;
diff --git a/common-lib/src/com/checkmarx/eclipse/common/preferences/Preferences.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/Preferences.java
new file mode 100644
index 00000000..293b9497
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/Preferences.java
@@ -0,0 +1,218 @@
+package com.checkmarx.eclipse.common.preferences;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+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.IMcpUninstallHandler;
+import com.checkmarx.eclipse.common.listener.ISettingsChangeNotifier;
+import com.checkmarx.eclipse.common.listener.IWorkspaceScanService;
+
+public class Preferences {
+
+ public static final String QUALIFIER = "com.checkmarx.eclipse";
+ public static final String API_KEY = "apiKey";
+ public static final String ADDITIONAL_OPTIONS = "additionalOptions";
+
+ // Tracks whether the currently-stored API_KEY has actually been confirmed against
+ // the server (Authenticator.doAuthentication succeeded)...
+ public static final String CREDENTIALS_VALIDATED = "credentialsValidated";
+
+ // Scanner Preference Keys (from CheckmarxPreferencePage)
+ public static final String PREF_ASCA_ENABLED = "scanner.asca.enabled";
+ public static final String PREF_OSS_ENABLED = "scanner.oss.enabled";
+ public static final String PREF_SECRETS_ENABLED = "scanner.secrets.enabled";
+ public static final String PREF_CONTAINERS_ENABLED = "scanner.containers.enabled";
+ public static final String PREF_IAC_ENABLED = "scanner.iac.enabled";
+ public static final String PREF_CONTAINERS_TOOL = "scanner.containers.tool";
+
+ // User Preferences (preserved when features toggle) - mirrors JetBrains pattern
+ public static final String USER_PREF_ASCA_ENABLED = "userPref.scanner.asca.enabled";
+ public static final String USER_PREF_OSS_ENABLED = "userPref.scanner.oss.enabled";
+ public static final String USER_PREF_SECRETS_ENABLED = "userPref.scanner.secrets.enabled";
+ public static final String USER_PREF_CONTAINERS_ENABLED = "userPref.scanner.containers.enabled";
+ public static final String USER_PREF_IAC_ENABLED = "userPref.scanner.iac.enabled";
+ public static final String USER_PREFERENCES_SET = "userPreferences.set";
+
+ public static final ScopedPreferenceStore STORE = new ScopedPreferenceStore(InstanceScope.INSTANCE, QUALIFIER);
+
+ // Handler for post-authentication UI setup (registered by devassist-lib)
+ private static IAuthenticationSuccessHandler authSuccessHandler;
+
+ // Notifiers for settings changes (registered by main plugin and devassist-lib).
+ // A List is used because both bundles register their own notifier for different
+ // purposes (UI panel refresh vs. scanner-state sync); a single-slot field would
+ // let one registration silently overwrite the other.
+ private static final List settingsChangeNotifiers = new CopyOnWriteArrayList<>();
+
+ // 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;
+
+ // Handler for uninstalling the Checkmarx MCP server configuration (registered by devassist-lib)
+ private static IMcpUninstallHandler mcpUninstallHandler;
+
+ private Preferences() {
+ }
+
+ public static String getPref(String key) {
+ return Platform.getPreferencesService().getString(Preferences.QUALIFIER, key, null, null);
+ }
+
+ public static String getApiKey() {
+ return getPref(API_KEY);
+ }
+
+ public static String getAdditionalOptions() {
+ return getPref(ADDITIONAL_OPTIONS);
+ }
+
+ public static void store(String key, String value) {
+ // Replaced Activator call with the ScopedPreferenceStore instance
+ STORE.setValue(key, value);
+ }
+
+ public static boolean isCredentialsValidated() {
+ return STORE.getBoolean(CREDENTIALS_VALIDATED);
+ }
+
+ 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;
+ }
+
+ public static IAuthenticationSuccessHandler getAuthenticationSuccessHandler() {
+ return authSuccessHandler;
+ }
+
+ public static void addSettingsChangeNotifier(ISettingsChangeNotifier notifier) {
+ settingsChangeNotifiers.add(notifier);
+ }
+
+ public static List getSettingsChangeNotifiers() {
+ return settingsChangeNotifiers;
+ }
+
+ public static void setWorkspaceScanService(IWorkspaceScanService service) {
+ workspaceScanService = service;
+ }
+
+ public static IWorkspaceScanService getWorkspaceScanService() {
+ return workspaceScanService;
+ }
+
+ public static void setMcpInstallHandler(IMcpInstallHandler handler) {
+ mcpInstallHandler = handler;
+ }
+
+ public static IMcpInstallHandler getMcpInstallHandler() {
+ return mcpInstallHandler;
+ }
+
+ public static void setMcpUninstallHandler(IMcpUninstallHandler handler) {
+ mcpUninstallHandler = handler;
+ }
+
+ public static IMcpUninstallHandler getMcpUninstallHandler() {
+ return mcpUninstallHandler;
+ }
+
+ // ============================================================================
+ // USER PREFERENCES - Preserve user's scanner choices across feature toggles
+ // Mirrors JetBrains GlobalSettingsState.setUserPreferences() pattern
+ // ============================================================================
+
+ /**
+ * Save user's current scanner preferences for preservation when features toggle.
+ * Called when user clicks OK/Apply on preferences page, or when a feature is about to disable.
+ *
+ * @param asca Enable/disable ASCA
+ * @param oss Enable/disable OSS
+ * @param secrets Enable/disable Secrets
+ * @param containers Enable/disable Containers
+ * @param iac Enable/disable IaC
+ */
+ public static void setUserPreferences(boolean asca, boolean oss, boolean secrets,
+ boolean containers, boolean iac) {
+ STORE.setValue(USER_PREF_ASCA_ENABLED, asca);
+ STORE.setValue(USER_PREF_OSS_ENABLED, oss);
+ STORE.setValue(USER_PREF_SECRETS_ENABLED, secrets);
+ STORE.setValue(USER_PREF_CONTAINERS_ENABLED, containers);
+ STORE.setValue(USER_PREF_IAC_ENABLED, iac);
+ STORE.setValue(USER_PREFERENCES_SET, true);
+ }
+
+ /**
+ * Restore user's previously saved preferences to current scanner settings.
+ * Called when a feature re-enables after being disabled.
+ *
+ * @return true if preferences were restored, false if no preferences saved
+ */
+ public static boolean applyUserPreferencesToCurrentSettings() {
+ if (!STORE.getBoolean(USER_PREFERENCES_SET)) {
+ return false; // No user preferences saved yet
+ }
+
+ boolean asca = STORE.getBoolean(USER_PREF_ASCA_ENABLED);
+ boolean oss = STORE.getBoolean(USER_PREF_OSS_ENABLED);
+ boolean secrets = STORE.getBoolean(USER_PREF_SECRETS_ENABLED);
+ boolean containers = STORE.getBoolean(USER_PREF_CONTAINERS_ENABLED);
+ boolean iac = STORE.getBoolean(USER_PREF_IAC_ENABLED);
+
+ // Apply to current settings
+ STORE.setValue(PREF_ASCA_ENABLED, asca);
+ STORE.setValue(PREF_OSS_ENABLED, oss);
+ STORE.setValue(PREF_SECRETS_ENABLED, secrets);
+ STORE.setValue(PREF_CONTAINERS_ENABLED, containers);
+ STORE.setValue(PREF_IAC_ENABLED, iac);
+
+ return true;
+ }
+
+ /**
+ * Check if user has any custom preferences saved.
+ * Used to determine if this is first time or existing user.
+ *
+ * @return true if preferences have been saved, false if default state
+ */
+ public static boolean getUserPreferencesSet() {
+ return STORE.getBoolean(USER_PREFERENCES_SET);
+ }
+
+ /**
+ * Save current scanner settings as user preferences.
+ * Called before disabling scanners to preserve user's choices.
+ */
+ public static void saveCurrentSettingsAsUserPreferences() {
+ boolean asca = STORE.getBoolean(PREF_ASCA_ENABLED);
+ boolean oss = STORE.getBoolean(PREF_OSS_ENABLED);
+ boolean secrets = STORE.getBoolean(PREF_SECRETS_ENABLED);
+ boolean containers = STORE.getBoolean(PREF_CONTAINERS_ENABLED);
+ boolean iac = STORE.getBoolean(PREF_IAC_ENABLED);
+
+ setUserPreferences(asca, oss, secrets, containers, iac);
+ }
+}
\ No newline at end of file
diff --git a/common-lib/src/com/checkmarx/eclipse/common/preferences/PreferencesPage.java b/common-lib/src/com/checkmarx/eclipse/common/preferences/PreferencesPage.java
new file mode 100644
index 00000000..d272318e
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/preferences/PreferencesPage.java
@@ -0,0 +1,567 @@
+package com.checkmarx.eclipse.common.preferences;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.jface.preference.StringFieldEditor;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
+import org.eclipse.ui.dialogs.PreferencesUtil;
+
+import com.checkmarx.eclipse.common.utils.PluginConstants;
+import com.checkmarx.eclipse.common.listener.IAuthenticationSuccessHandler;
+import com.checkmarx.eclipse.common.listener.IMcpUninstallCallback;
+import com.checkmarx.eclipse.common.listener.IMcpUninstallHandler;
+import com.checkmarx.eclipse.common.listener.ISettingsChangeNotifier;
+import com.checkmarx.eclipse.common.runner.Authenticator;
+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 One Assist"
+ * (Realtime Scanners) would otherwise still unconditionally fire
+ * TOPIC_APPLY_SETTINGS below and refresh the unrelated Checkmarx One scan view.
+ */
+ private StringFieldEditor apiKeyField;
+ private StringFieldEditor additionalParamsField;
+ private String initialApiKey;
+ private String initialAdditionalOptions;
+ private Link realtimeScannersLink;
+
+
+ public PreferencesPage() {
+ super(GRID);
+ // Replaced Activator preference store listener with Preferences.STORE
+ Preferences.STORE.addPropertyChangeListener(this::handlePropertyChange);
+ }
+
+ private void handlePropertyChange(PropertyChangeEvent event) {
+ refreshRealtimeScannersLink();
+ }
+
+ /**
+ * Shows the "Go to Realtime Scanners" link only while the user is logged in -
+ * the page it opens has no meaningful content to configure otherwise.
+ */
+ private void refreshRealtimeScannersLink() {
+ if (realtimeScannersLink != null && !realtimeScannersLink.isDisposed()) {
+ boolean isLoggedIn = Preferences.isAuthenticated();
+
+ realtimeScannersLink.setVisible(isLoggedIn);
+
+ if (realtimeScannersLink.getLayoutData() instanceof GridData) {
+ ((GridData) realtimeScannersLink.getLayoutData()).exclude = !isLoggedIn;
+ }
+
+ // Re-layout the parent so other controls adjust dynamically
+ Composite parent = realtimeScannersLink.getParent();
+ if (parent != null && !parent.isDisposed()) {
+ parent.layout(true, true);
+ }
+ }
+ }
+
+ @Override
+ public void init(IWorkbench workbench) {
+ setPreferenceStore(Preferences.STORE);
+ setMessage(PluginConstants.CHECKMARX_ONE);
+ }
+
+ @Override
+ protected void createFieldEditors() {
+ Composite topComposite = new Composite(getFieldEditorParent(), SWT.NONE);
+ GridData topGridData = new GridData();
+ topGridData.horizontalAlignment = GridData.FILL;
+ topGridData.verticalAlignment = GridData.FILL;
+ topGridData.grabExcessHorizontalSpace = true;
+ topComposite.setLayoutData(topGridData);
+
+ getFieldEditorParent().setLayoutData(topGridData);
+
+ GridLayout parentLayout = new GridLayout();
+ parentLayout.numColumns = 1;
+ parentLayout.horizontalSpacing = 0;
+ parentLayout.verticalSpacing = 0;
+ parentLayout.marginHeight = 0;
+ parentLayout.marginWidth = 0;
+ topComposite.setLayout(parentLayout);
+
+ // 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(fieldsComposite);
+ textControl.setEchoChar('*');
+
+ StringFieldEditor additionalParams = new StringFieldEditor(Preferences.ADDITIONAL_OPTIONS,
+ PluginConstants.PREFERENCES_ADDITIONAL_OPTIONS, StringFieldEditor.UNLIMITED,
+ StringFieldEditor.VALIDATE_ON_KEY_STROKE, fieldsComposite);
+ additionalParamsField = additionalParams;
+ addField(additionalParams);
+
+ // 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();
+
+
+ boolean isConnected = (Preferences.isAuthenticated() && StringUtils.isNotBlank(Preferences.getApiKey()));
+
+ // 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);
+
+ 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();
+ }
+ }
+ });
+
+ 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.
+ final Button[] logoutButtonHolder = new Button[1];
+
+ 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);
+ connectionButton.setEnabled(false);
+
+ }
+
+ textControl.addModifyListener(e -> {
+ boolean hasApiKey = StringUtils.isNotBlank(textControl.getText());
+ // Fetch live authentication status rather than relying on the static isConnected closure variable
+ boolean currentlyConnected = Preferences.isAuthenticated() && StringUtils.isNotBlank(Preferences.getApiKey());
+ // API key is mandatory to enable Connect; also disable Connect if the key
+ // matches the already-validated key (it stays connected in that case).
+ connectionButton.setEnabled(currentlyConnected ? false : hasApiKey);
+ });
+ connectionButton.addSelectionListener(new SelectionAdapter() {
+
+ public void widgetSelected(SelectionEvent e) {
+
+ String apiKey_str = apiKey.getStringValue();
+
+ // API key is mandatory — don't attempt authentication without it.
+
+ if (StringUtils.isBlank(apiKey_str)) {
+ MessageDialog.openWarning(getShell(), "Missing API Key",
+ "Please enter an API key before attempting to connect.");
+ // Ensure Connect remains disabled until user enters a key
+ connectionButton.setEnabled(false);
+ return;
+ }
+
+ 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
+ // leaves the flow half-finished and the welcome dialog never shown.
+ if (logoutButtonHolder[0] != null && !logoutButtonHolder[0].isDisposed()) {
+ logoutButtonHolder[0].setEnabled(false);
+ }
+
+ CompletableFuture.supplyAsync(() -> {
+ try {
+ return Authenticator.INSTANCE.doAuthentication(apiKey_str, additionalParams_str);
+ } catch (Throwable t) {
+ CxLogger.error(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, t.getMessage()), new Exception(t));
+ return t.getMessage();
+ }
+ }).thenAccept((result) -> Display.getDefault().syncExec(() -> {
+ // Guard every widget touch below: if the preferences dialog was closed
+ // 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.
+
+ // Show welcome dialog on successful authentication. The "Validating..."
+ // message is left on screen (not switched to "Connected") until the
+ // welcome dialog is actually about to appear, so the label never claims
+ // success before the user sees the welcome page.
+ if (result != null && result.contains(PluginConstants.AUTH_SUCCESS_PATTERN)) {
+ // The key was only just validated by "Test Connection" - it isn't persisted
+ // to the store until the user clicks OK/Apply on this dialog, which they may
+ // never do once they see the Welcome page. Persist it now so
+ // isUserAuthenticated() (checked by ProjectLifecycleListener, and by
+ // anything else gated on login) actually sees it.
+ Preferences.STORE.setValue(Preferences.API_KEY, apiKey_str);
+ Preferences.STORE.setValue(Preferences.ADDITIONAL_OPTIONS, additionalParams_str);
+ Preferences.setCredentialsValidated(true);
+ // 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
+ // so they can switch from the credentials panel to the actual work views
+ for (ISettingsChangeNotifier notifier : Preferences.getSettingsChangeNotifiers()) {
+ notifier.notifySettingsApplied();
+ }
+
+ checkMCPStatus(logoutButtonHolder, connectionLabel, apiKey_str, additionalParams_str, result);
+ } else {
+ // Authentication failed - the flow ends here with no welcome dialog,
+ // 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();
+ }
+ if (logoutButtonHolder[0] != null && !logoutButtonHolder[0].isDisposed()) {
+ logoutButtonHolder[0].setEnabled(false);
+ }
+ }
+ }));
+ }
+ });
+
+ Button logoutButton = new Button(buttonsComposite, SWT.PUSH);
+ logoutButtonHolder[0] = logoutButton;
+ 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) {
+ 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);
+ connectionButton.setEnabled(true);
+ textControl.setEnabled(true);
+ logoutButton.setEnabled(false);
+ connectionLabel.setText(PluginConstants.LOGOUT_SUCCESS_MESSAGE);
+ setStatusLabelColor(connectionLabel, true);
+ refreshRealtimeScannersLink();
+ getFieldEditorParent().layout();
+
+ // Redraws the missing-credentials panel in CheckmarxView/CxFindingsView right
+ // away. Without this, they only learn credentials are gone once performOk()
+ // runs (i.e. the user clicks OK/Apply) - if they instead Cancel or just close
+ // the dialog after Logout, both views kept showing stale "connected" content.
+ // Notify main plugin that settings have changed
+ for (ISettingsChangeNotifier notifier : Preferences.getSettingsChangeNotifiers()) {
+ notifier.notifySettingsApplied();
+ }
+
+ uninstallMCP();
+ }
+ });
+
+ spacer(topComposite);
+ 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
+ refreshRealtimeScannersLink();
+
+ realtimeScannersLink.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ 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;
+ }
+ // 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 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
+ public boolean performOk() {
+ boolean ok = super.performOk();
+
+ if (ok) {
+ /*
+ * Only notify listeners (e.g. the Checkmarx One scan view refresh) if this
+ * page's own settings actually changed in this session. Without this guard,
+ * merely having visited this page in the same Preferences dialog session as
+ * the unrelated "Checkmarx Scanner Configuration" (Realtime Scanners) page -
+ * a sibling top-level page in the same tree - is enough for Eclipse to call
+ * 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)
+ || !java.util.Objects.equals(currentAdditionalOptions, initialAdditionalOptions);
+
+ if (settingsActuallyChanged) {
+ // Notify main plugin that settings have changed
+ for (ISettingsChangeNotifier notifier : Preferences.getSettingsChangeNotifiers()) {
+ notifier.notifySettingsApplied();
+ }
+ }
+ }
+
+ return ok;
+ }
+
+ /**
+ * Checks the MCP (Checkmarx One Assist) status from the server asynchronously and updates the UI accordingly.
+ * @param logoutButtonHolder
+ * @param connectionLabel
+ * @param apiKey_str
+ * @param additionalParams_str
+ * @param result
+ */
+ private void checkMCPStatus(final Button[] logoutButtonHolder, Label connectionLabel, String apiKey_str,
+ String additionalParams_str, String result) {
+ // Fetch MCP enabled status from server asynchronously
+ CompletableFuture.supplyAsync(() -> {
+ try {
+ return TenantSettingsProvider.INSTANCE.isAiMcpServerEnabled(apiKey_str,
+ additionalParams_str);
+ } catch (Exception ex) {
+ CxLogger.error("Failed to fetch MCP status", ex);
+ return false;
+ }
+ }).thenAccept((mcpEnabled) -> Display.getDefault().syncExec(() -> {
+ if (!connectionLabel.isDisposed()) {
+ connectionLabel.setText(mapAuthResult(result));
+ setStatusLabelColor(connectionLabel, true);
+ }
+ if (!getFieldEditorParent().isDisposed()) {
+ getFieldEditorParent().layout();
+ }
+ // 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);
+ } else {
+ CxLogger.warning(
+ "[PREFS] No authentication success handler registered - welcome dialog skipped");
+ if (logoutButtonHolder[0] != null && !logoutButtonHolder[0].isDisposed()) {
+ logoutButtonHolder[0].setEnabled(true);
+ }
+ }
+ }));
+ }
+
+ /**
+ * Uninstalls the Checkmarx MCP configuration after a successful logout.
+ * Delegates to the handler registered by devassist-lib.
+ */
+ private void uninstallMCP() {
+ IMcpUninstallHandler handler = Preferences.getMcpUninstallHandler();
+ if (handler == null) {
+ CxLogger.info("[PREFS] MCP uninstall handler not registered - skipping MCP uninstall");
+ return;
+ }
+
+ CxLogger.info("[PREFS] Triggering MCP uninstall after logout...");
+ handler.uninstallMcp(new IMcpUninstallCallback() {
+ @Override
+ public void onSuccess() {
+ CxLogger.info("[PREFS] ✓ Checkmarx MCP uninstalled successfully from Copilot preferences.");
+ }
+
+ @Override
+ public void onNotFound() {
+ CxLogger.info("[PREFS] No Checkmarx MCP configuration entry found to uninstall (may not have been installed).");
+ }
+
+ @Override
+ public void onFailure(String errorMessage) {
+ CxLogger.error("[PREFS] ✗ Failed to uninstall Checkmarx MCP: " + errorMessage, new Exception(errorMessage));
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/common-lib/src/com/checkmarx/eclipse/common/runner/Authenticator.java b/common-lib/src/com/checkmarx/eclipse/common/runner/Authenticator.java
new file mode 100644
index 00000000..d9a9f7b8
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/runner/Authenticator.java
@@ -0,0 +1,26 @@
+package com.checkmarx.eclipse.common.runner;
+
+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 Authenticator() {
+ // Private constructor to prevent instantiation
+ }
+
+ protected static final String AUTH_STATUS = "Authentication Status: ";
+ public static final Authenticator INSTANCE = new Authenticator();
+
+ public String doAuthentication(String apiKey, String additionalParams) {
+ try {
+ String cxValidateOutput = new WrapperProvider().authValidate(apiKey, additionalParams);
+ CxLogger.info(String.format(PluginConstants.INFO_AUTHENTICATION_STATUS, cxValidateOutput));
+ return cxValidateOutput;
+ } catch (Exception e) {
+ CxLogger.error(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, e.getMessage()), e);
+ return e.getMessage();
+ }
+ }
+}
\ No newline at end of file
diff --git a/common-lib/src/com/checkmarx/eclipse/common/runner/TenantSettingsProvider.java b/common-lib/src/com/checkmarx/eclipse/common/runner/TenantSettingsProvider.java
new file mode 100644
index 00000000..2ed6a381
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/runner/TenantSettingsProvider.java
@@ -0,0 +1,38 @@
+package com.checkmarx.eclipse.common.runner;
+
+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 String LOG_PREFIX = "[TENANT_SETTINGS_PROVIDER] ";
+ public static final TenantSettingsProvider INSTANCE = new TenantSettingsProvider();
+
+ private TenantSettingsProvider() {
+ }
+
+ /**
+ * Check if AI MCP (Checkmarx One Assist) is enabled for the current tenant
+ *
+ * @param apiKey API key for authentication
+ * @param additionalParams Additional parameters for the CxWrapper
+ * @return true if MCP is enabled, false otherwise
+ */
+ public boolean isAiMcpServerEnabled(String apiKey, String additionalParams) {
+ if (apiKey == null || apiKey.trim().isEmpty()) {
+ return false;
+ }
+ try {
+ boolean mcpEnabled = new WrapperProvider().isAiMcpServerEnabled(apiKey, additionalParams);
+ CxLogger.info(String.format("MCP Server Status: %s", mcpEnabled ? "ENABLED" : "DISABLED"));
+ return mcpEnabled;
+ } 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/CxLogger.java b/common-lib/src/com/checkmarx/eclipse/common/utils/CxLogger.java
new file mode 100644
index 00000000..099f8e14
--- /dev/null
+++ b/common-lib/src/com/checkmarx/eclipse/common/utils/CxLogger.java
@@ -0,0 +1,58 @@
+package com.checkmarx.eclipse.common.utils;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * Class responsible to add entries to Eclipse Error Log perspective
+ *
+ * @author HugoMa
+ *
+ */
+public class CxLogger {
+
+ private static final Bundle BUNDLE = FrameworkUtil.getBundle(CxLogger.class);
+ private static final ILog LOGGER = Platform.getLog(BUNDLE);
+
+ /**
+ * Add entry as error
+ *
+ * @param msg
+ * @param e
+ */
+ public static void error(String msg, Exception e) {
+ log(Status.ERROR, msg, e);
+ }
+
+ /**
+ * Add entry as warning
+ *
+ * @param msg
+ */
+ public static void warning(String msg) {
+ log(Status.WARNING, msg, null);
+ }
+
+ /**
+ * Add entry as info
+ *
+ * @param msg
+ */
+ public static void info(String msg) {
+ log(Status.INFO, msg, null);
+ }
+
+ /**
+ * Add entry to Error Log
+ *
+ * @param status
+ * @param msg
+ * @param e
+ */
+ private static void log(int status, String msg, Exception e) {
+ LOGGER.log(new Status(status, BUNDLE.getSymbolicName(), msg, e));
+ }
+}
diff --git a/checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/utils/PluginConstants.java b/common-lib/src/com/checkmarx/eclipse/common/utils/PluginConstants.java
similarity index 67%
rename from checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/utils/PluginConstants.java
rename to common-lib/src/com/checkmarx/eclipse/common/utils/PluginConstants.java
index 524b1136..2e53df17 100644
--- a/checkmarx-ast-eclipse-plugin/src/com/checkmarx/eclipse/utils/PluginConstants.java
+++ b/common-lib/src/com/checkmarx/eclipse/common/utils/PluginConstants.java
@@ -1,6 +1,10 @@
-package com.checkmarx.eclipse.utils;
+package com.checkmarx.eclipse.common.utils;
+
+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";
@@ -21,6 +25,7 @@ public class PluginConstants {
public static final String BFL_NOT_FOUND = "Best fix Location not available for given results";
public static final String TOOLBAR_ACTION_PREFERENCES = "Preferences";
public static final String TOOLBAR_ACTION_CLEAR_RESULTS = "Clear results section";
+ public static final String FINDINGS_PROMO_DESCRIPTION = "Checkmarx AI (Cx Assist) provides real-time threat detection and helps you avoid vulnerabilities before they happen.";
/******************************** LOG VIEW: ERRORS ********************************/
@@ -50,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.";
@@ -60,11 +64,22 @@ 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...";
-
- /******************************** TOPICS ********************************/
- public static final String TOPIC_APPLY_SETTINGS = "ApplySettings";
+ 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 ********************************/
public static final String PROBLEM_SOURCE_ID = "CheckmarxEclipsePlugin";
@@ -136,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..9a857785
--- /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 filter the results.
+ * @param filter
+ * @return
+ * @throws Exception
+ */
+ public List getProjects(String filter) throws Exception {
+ return CxWrapperFactory.build().projectList(filter);
+ }
+
+ /**
+ * 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
new file mode 100644
index 00000000..cca0ae6a
--- /dev/null
+++ b/devassist-lib/.classpath
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/devassist-lib/.gitignore b/devassist-lib/.gitignore
new file mode 100644
index 00000000..92145bce
--- /dev/null
+++ b/devassist-lib/.gitignore
@@ -0,0 +1,2 @@
+/bin/
+/target/
\ No newline at end of file
diff --git a/devassist-lib/.project b/devassist-lib/.project
new file mode 100644
index 00000000..7c973ce5
--- /dev/null
+++ b/devassist-lib/.project
@@ -0,0 +1,34 @@
+
+
+ devassist-lib
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/devassist-lib/.settings/org.eclipse.core.resources.prefs b/devassist-lib/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000..99f26c02
--- /dev/null
+++ b/devassist-lib/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/devassist-lib/.settings/org.eclipse.m2e.core.prefs b/devassist-lib/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 00000000..f897a7f1
--- /dev/null
+++ b/devassist-lib/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/devassist-lib/META-INF/MANIFEST.MF b/devassist-lib/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..326e02f3
--- /dev/null
+++ b/devassist-lib/META-INF/MANIFEST.MF
@@ -0,0 +1,36 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: DevAssist Library
+Bundle-SymbolicName: com.checkmarx.eclipse.devassist;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: com.checkmarx.eclipse.devassist.Activator
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-17
+Bundle-ClassPath: .
+Require-Bundle: com.checkmarx.eclipse.common,
+ org.eclipse.ui,
+ org.eclipse.ui.workbench,
+ org.eclipse.ui.workbench.texteditor,
+ org.eclipse.ui.editors,
+ org.eclipse.ui.ide,
+ org.eclipse.ui.genericeditor,
+ org.eclipse.core.runtime,
+ org.eclipse.core.resources,
+ org.eclipse.core.commands,
+ org.eclipse.jface,
+ org.eclipse.jface.text,
+ org.eclipse.swt,
+ org.eclipse.jgit,
+ org.eclipse.e4.core.services,
+ org.eclipse.e4.ui.css.swt.theme,
+ org.eclipse.jdt.ui
+Import-Package: com.fasterxml.jackson.annotation,
+ com.fasterxml.jackson.core,
+ com.fasterxml.jackson.core.type,
+ com.fasterxml.jackson.databind,
+ org.eclipse.mylyn.commons.ui.dialogs,
+ org.osgi.service.event;version="1.4.1"
+Export-Package: com.checkmarx.eclipse.devassist.backend,
+ com.checkmarx.eclipse.devassist.backend.listener,
+ com.checkmarx.eclipse.devassist.ignore,
+ com.checkmarx.eclipse.devassist.ui.findings.ignore
diff --git a/devassist-lib/build.properties b/devassist-lib/build.properties
new file mode 100644
index 00000000..f62b840c
--- /dev/null
+++ b/devassist-lib/build.properties
@@ -0,0 +1,6 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ plugin.xml,\
+ icons/,\
+ .
\ No newline at end of file
diff --git a/devassist-lib/icons/CxFlatLogo16x16.png b/devassist-lib/icons/CxFlatLogo16x16.png
new file mode 100644
index 00000000..4176de23
Binary files /dev/null and b/devassist-lib/icons/CxFlatLogo16x16.png differ
diff --git a/devassist-lib/icons/checkmarx-plugin-13_dark.png b/devassist-lib/icons/checkmarx-plugin-13_dark.png
new file mode 100644
index 00000000..18aa6461
Binary files /dev/null and b/devassist-lib/icons/checkmarx-plugin-13_dark.png differ
diff --git a/devassist-lib/icons/critical_16.svg b/devassist-lib/icons/critical_16.svg
new file mode 100644
index 00000000..6e1929e8
--- /dev/null
+++ b/devassist-lib/icons/critical_16.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/critical_16_dark.svg b/devassist-lib/icons/critical_16_dark.svg
new file mode 100644
index 00000000..9c89888d
--- /dev/null
+++ b/devassist-lib/icons/critical_16_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/critical_20.svg b/devassist-lib/icons/critical_20.svg
new file mode 100644
index 00000000..5a297484
--- /dev/null
+++ b/devassist-lib/icons/critical_20.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/critical_20_dark.svg b/devassist-lib/icons/critical_20_dark.svg
new file mode 100644
index 00000000..74a7154a
--- /dev/null
+++ b/devassist-lib/icons/critical_20_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/high_16.svg b/devassist-lib/icons/high_16.svg
new file mode 100644
index 00000000..4c815e84
--- /dev/null
+++ b/devassist-lib/icons/high_16.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/high_16_dark.svg b/devassist-lib/icons/high_16_dark.svg
new file mode 100644
index 00000000..d9b8a81f
--- /dev/null
+++ b/devassist-lib/icons/high_16_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/high_20.svg b/devassist-lib/icons/high_20.svg
new file mode 100644
index 00000000..167be4d1
--- /dev/null
+++ b/devassist-lib/icons/high_20.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/high_20_dark.svg b/devassist-lib/icons/high_20_dark.svg
new file mode 100644
index 00000000..292e26a0
--- /dev/null
+++ b/devassist-lib/icons/high_20_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/ignored_16.svg b/devassist-lib/icons/ignored_16.svg
new file mode 100644
index 00000000..4ec04da0
--- /dev/null
+++ b/devassist-lib/icons/ignored_16.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/ignored_16_dark.svg b/devassist-lib/icons/ignored_16_dark.svg
new file mode 100644
index 00000000..20246d56
--- /dev/null
+++ b/devassist-lib/icons/ignored_16_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/ignored_20.svg b/devassist-lib/icons/ignored_20.svg
new file mode 100644
index 00000000..f8b60d31
--- /dev/null
+++ b/devassist-lib/icons/ignored_20.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/ignored_20_dark.svg b/devassist-lib/icons/ignored_20_dark.svg
new file mode 100644
index 00000000..06138d2a
--- /dev/null
+++ b/devassist-lib/icons/ignored_20_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-critical.svg b/devassist-lib/icons/ignored_card/card-containers-critical.svg
new file mode 100644
index 00000000..a8e19cae
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-critical.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-critical_dark.svg b/devassist-lib/icons/ignored_card/card-containers-critical_dark.svg
new file mode 100644
index 00000000..06d6848b
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-critical_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-high.svg b/devassist-lib/icons/ignored_card/card-containers-high.svg
new file mode 100644
index 00000000..8aed7382
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-high.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-high_dark.svg b/devassist-lib/icons/ignored_card/card-containers-high_dark.svg
new file mode 100644
index 00000000..b586724e
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-high_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-low.svg b/devassist-lib/icons/ignored_card/card-containers-low.svg
new file mode 100644
index 00000000..9051c831
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-low.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-low_dark.svg b/devassist-lib/icons/ignored_card/card-containers-low_dark.svg
new file mode 100644
index 00000000..96efafe1
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-low_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-malicious.svg b/devassist-lib/icons/ignored_card/card-containers-malicious.svg
new file mode 100644
index 00000000..6dd33af0
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-malicious.svg
@@ -0,0 +1,12 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-malicious_dark.svg b/devassist-lib/icons/ignored_card/card-containers-malicious_dark.svg
new file mode 100644
index 00000000..dd4a0b2a
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-malicious_dark.svg
@@ -0,0 +1,12 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-medium.svg b/devassist-lib/icons/ignored_card/card-containers-medium.svg
new file mode 100644
index 00000000..e4e5c61b
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-medium.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-containers-medium_dark.svg b/devassist-lib/icons/ignored_card/card-containers-medium_dark.svg
new file mode 100644
index 00000000..84a3ecfb
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-containers-medium_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-critical.svg b/devassist-lib/icons/ignored_card/card-package-critical.svg
new file mode 100644
index 00000000..7b80b169
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-critical.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-critical_dark.svg b/devassist-lib/icons/ignored_card/card-package-critical_dark.svg
new file mode 100644
index 00000000..4f622d39
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-critical_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-high.svg b/devassist-lib/icons/ignored_card/card-package-high.svg
new file mode 100644
index 00000000..dcb0319d
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-high.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-high_dark.svg b/devassist-lib/icons/ignored_card/card-package-high_dark.svg
new file mode 100644
index 00000000..c09f9ed5
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-high_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-low.svg b/devassist-lib/icons/ignored_card/card-package-low.svg
new file mode 100644
index 00000000..8615dd0b
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-low.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-low_dark.svg b/devassist-lib/icons/ignored_card/card-package-low_dark.svg
new file mode 100644
index 00000000..f6bc934c
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-low_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-malicious.svg b/devassist-lib/icons/ignored_card/card-package-malicious.svg
new file mode 100644
index 00000000..38856995
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-malicious.svg
@@ -0,0 +1,12 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-malicious_dark.svg b/devassist-lib/icons/ignored_card/card-package-malicious_dark.svg
new file mode 100644
index 00000000..4bdb763b
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-malicious_dark.svg
@@ -0,0 +1,12 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-medium.svg b/devassist-lib/icons/ignored_card/card-package-medium.svg
new file mode 100644
index 00000000..4c2ee230
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-medium.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-package-medium_dark.svg b/devassist-lib/icons/ignored_card/card-package-medium_dark.svg
new file mode 100644
index 00000000..1ae43a36
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-package-medium_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-critical.svg b/devassist-lib/icons/ignored_card/card-secret-critical.svg
new file mode 100644
index 00000000..600777a2
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-critical.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-critical_dark.svg b/devassist-lib/icons/ignored_card/card-secret-critical_dark.svg
new file mode 100644
index 00000000..eceb3949
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-critical_dark.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-high.svg b/devassist-lib/icons/ignored_card/card-secret-high.svg
new file mode 100644
index 00000000..d8b99816
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-high.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-high_dark.svg b/devassist-lib/icons/ignored_card/card-secret-high_dark.svg
new file mode 100644
index 00000000..2cd599a1
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-high_dark.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-low.svg b/devassist-lib/icons/ignored_card/card-secret-low.svg
new file mode 100644
index 00000000..68753134
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-low.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-low_dark.svg b/devassist-lib/icons/ignored_card/card-secret-low_dark.svg
new file mode 100644
index 00000000..1ade2bba
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-low_dark.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-malicious.svg b/devassist-lib/icons/ignored_card/card-secret-malicious.svg
new file mode 100644
index 00000000..a3e0ad6f
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-malicious.svg
@@ -0,0 +1,13 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-malicious_dark.svg b/devassist-lib/icons/ignored_card/card-secret-malicious_dark.svg
new file mode 100644
index 00000000..247688bf
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-malicious_dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-medium.svg b/devassist-lib/icons/ignored_card/card-secret-medium.svg
new file mode 100644
index 00000000..295ef9a1
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-medium.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-secret-medium_dark.svg b/devassist-lib/icons/ignored_card/card-secret-medium_dark.svg
new file mode 100644
index 00000000..f1b7d6a1
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-secret-medium_dark.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-critical.svg b/devassist-lib/icons/ignored_card/card-vulnerability-critical.svg
new file mode 100644
index 00000000..c6d8da79
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-critical.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-critical_dark.svg b/devassist-lib/icons/ignored_card/card-vulnerability-critical_dark.svg
new file mode 100644
index 00000000..557ee013
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-critical_dark.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-high.svg b/devassist-lib/icons/ignored_card/card-vulnerability-high.svg
new file mode 100644
index 00000000..02fe3b64
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-high.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-high_dark.svg b/devassist-lib/icons/ignored_card/card-vulnerability-high_dark.svg
new file mode 100644
index 00000000..8c82a8bc
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-high_dark.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-low.svg b/devassist-lib/icons/ignored_card/card-vulnerability-low.svg
new file mode 100644
index 00000000..dbf34b5e
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-low.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-low_dark.svg b/devassist-lib/icons/ignored_card/card-vulnerability-low_dark.svg
new file mode 100644
index 00000000..08f2243a
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-low_dark.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-malicious.svg b/devassist-lib/icons/ignored_card/card-vulnerability-malicious.svg
new file mode 100644
index 00000000..f22a83c1
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-malicious.svg
@@ -0,0 +1,13 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-malicious_dark.svg b/devassist-lib/icons/ignored_card/card-vulnerability-malicious_dark.svg
new file mode 100644
index 00000000..3f1c080d
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-malicious_dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-medium.svg b/devassist-lib/icons/ignored_card/card-vulnerability-medium.svg
new file mode 100644
index 00000000..e6c7461b
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-medium.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/ignored_card/card-vulnerability-medium_dark.svg b/devassist-lib/icons/ignored_card/card-vulnerability-medium_dark.svg
new file mode 100644
index 00000000..87822132
--- /dev/null
+++ b/devassist-lib/icons/ignored_card/card-vulnerability-medium_dark.svg
@@ -0,0 +1,6 @@
+
diff --git a/devassist-lib/icons/low_16.svg b/devassist-lib/icons/low_16.svg
new file mode 100644
index 00000000..40b203e4
--- /dev/null
+++ b/devassist-lib/icons/low_16.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/low_16_dark.svg b/devassist-lib/icons/low_16_dark.svg
new file mode 100644
index 00000000..69f9b3a6
--- /dev/null
+++ b/devassist-lib/icons/low_16_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/low_20.svg b/devassist-lib/icons/low_20.svg
new file mode 100644
index 00000000..0ad469eb
--- /dev/null
+++ b/devassist-lib/icons/low_20.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/low_20_dark.svg b/devassist-lib/icons/low_20_dark.svg
new file mode 100644
index 00000000..b4310c02
--- /dev/null
+++ b/devassist-lib/icons/low_20_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/malicious_16.svg b/devassist-lib/icons/malicious_16.svg
new file mode 100644
index 00000000..32a94bd0
--- /dev/null
+++ b/devassist-lib/icons/malicious_16.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/malicious_16_dark.svg b/devassist-lib/icons/malicious_16_dark.svg
new file mode 100644
index 00000000..32a94bd0
--- /dev/null
+++ b/devassist-lib/icons/malicious_16_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/malicious_20.svg b/devassist-lib/icons/malicious_20.svg
new file mode 100644
index 00000000..946f3889
--- /dev/null
+++ b/devassist-lib/icons/malicious_20.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/malicious_20_dark.svg b/devassist-lib/icons/malicious_20_dark.svg
new file mode 100644
index 00000000..032df876
--- /dev/null
+++ b/devassist-lib/icons/malicious_20_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/medium_16.svg b/devassist-lib/icons/medium_16.svg
new file mode 100644
index 00000000..3a6cda49
--- /dev/null
+++ b/devassist-lib/icons/medium_16.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/medium_16_dark.svg b/devassist-lib/icons/medium_16_dark.svg
new file mode 100644
index 00000000..5be2c823
--- /dev/null
+++ b/devassist-lib/icons/medium_16_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/medium_20.svg b/devassist-lib/icons/medium_20.svg
new file mode 100644
index 00000000..4117ba0e
--- /dev/null
+++ b/devassist-lib/icons/medium_20.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/medium_20_dark.svg b/devassist-lib/icons/medium_20_dark.svg
new file mode 100644
index 00000000..8cd8ec41
--- /dev/null
+++ b/devassist-lib/icons/medium_20_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/ok_16.svg b/devassist-lib/icons/ok_16.svg
new file mode 100644
index 00000000..21fa16ef
--- /dev/null
+++ b/devassist-lib/icons/ok_16.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/ok_16_dark.svg b/devassist-lib/icons/ok_16_dark.svg
new file mode 100644
index 00000000..21fa16ef
--- /dev/null
+++ b/devassist-lib/icons/ok_16_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/critical.svg b/devassist-lib/icons/severity_16/critical.svg
new file mode 100644
index 00000000..6e1929e8
--- /dev/null
+++ b/devassist-lib/icons/severity_16/critical.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/critical_dark.svg b/devassist-lib/icons/severity_16/critical_dark.svg
new file mode 100644
index 00000000..9c89888d
--- /dev/null
+++ b/devassist-lib/icons/severity_16/critical_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/high.svg b/devassist-lib/icons/severity_16/high.svg
new file mode 100644
index 00000000..4c815e84
--- /dev/null
+++ b/devassist-lib/icons/severity_16/high.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/high_dark.svg b/devassist-lib/icons/severity_16/high_dark.svg
new file mode 100644
index 00000000..d9b8a81f
--- /dev/null
+++ b/devassist-lib/icons/severity_16/high_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/ignored.svg b/devassist-lib/icons/severity_16/ignored.svg
new file mode 100644
index 00000000..4ec04da0
--- /dev/null
+++ b/devassist-lib/icons/severity_16/ignored.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/ignored_dark.svg b/devassist-lib/icons/severity_16/ignored_dark.svg
new file mode 100644
index 00000000..20246d56
--- /dev/null
+++ b/devassist-lib/icons/severity_16/ignored_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/low.svg b/devassist-lib/icons/severity_16/low.svg
new file mode 100644
index 00000000..40b203e4
--- /dev/null
+++ b/devassist-lib/icons/severity_16/low.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/low_dark.svg b/devassist-lib/icons/severity_16/low_dark.svg
new file mode 100644
index 00000000..69f9b3a6
--- /dev/null
+++ b/devassist-lib/icons/severity_16/low_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/malicious.svg b/devassist-lib/icons/severity_16/malicious.svg
new file mode 100644
index 00000000..32a94bd0
--- /dev/null
+++ b/devassist-lib/icons/severity_16/malicious.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/malicious_dark.svg b/devassist-lib/icons/severity_16/malicious_dark.svg
new file mode 100644
index 00000000..32a94bd0
--- /dev/null
+++ b/devassist-lib/icons/severity_16/malicious_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/medium.svg b/devassist-lib/icons/severity_16/medium.svg
new file mode 100644
index 00000000..3a6cda49
--- /dev/null
+++ b/devassist-lib/icons/severity_16/medium.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/medium_dark.svg b/devassist-lib/icons/severity_16/medium_dark.svg
new file mode 100644
index 00000000..5be2c823
--- /dev/null
+++ b/devassist-lib/icons/severity_16/medium_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/ok.svg b/devassist-lib/icons/severity_16/ok.svg
new file mode 100644
index 00000000..21fa16ef
--- /dev/null
+++ b/devassist-lib/icons/severity_16/ok.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/ok_dark.svg b/devassist-lib/icons/severity_16/ok_dark.svg
new file mode 100644
index 00000000..21fa16ef
--- /dev/null
+++ b/devassist-lib/icons/severity_16/ok_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_16/unknown.svg b/devassist-lib/icons/severity_16/unknown.svg
new file mode 100644
index 00000000..d63f29bf
--- /dev/null
+++ b/devassist-lib/icons/severity_16/unknown.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/severity_16/unknown_dark.svg b/devassist-lib/icons/severity_16/unknown_dark.svg
new file mode 100644
index 00000000..a5270a2a
--- /dev/null
+++ b/devassist-lib/icons/severity_16/unknown_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/severity_20/critical.svg b/devassist-lib/icons/severity_20/critical.svg
new file mode 100644
index 00000000..5a297484
--- /dev/null
+++ b/devassist-lib/icons/severity_20/critical.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/critical_dark.svg b/devassist-lib/icons/severity_20/critical_dark.svg
new file mode 100644
index 00000000..74a7154a
--- /dev/null
+++ b/devassist-lib/icons/severity_20/critical_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/high.svg b/devassist-lib/icons/severity_20/high.svg
new file mode 100644
index 00000000..167be4d1
--- /dev/null
+++ b/devassist-lib/icons/severity_20/high.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/high_dark.svg b/devassist-lib/icons/severity_20/high_dark.svg
new file mode 100644
index 00000000..292e26a0
--- /dev/null
+++ b/devassist-lib/icons/severity_20/high_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/ignored.svg b/devassist-lib/icons/severity_20/ignored.svg
new file mode 100644
index 00000000..f8b60d31
--- /dev/null
+++ b/devassist-lib/icons/severity_20/ignored.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/ignored_dark.svg b/devassist-lib/icons/severity_20/ignored_dark.svg
new file mode 100644
index 00000000..06138d2a
--- /dev/null
+++ b/devassist-lib/icons/severity_20/ignored_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/low.svg b/devassist-lib/icons/severity_20/low.svg
new file mode 100644
index 00000000..0ad469eb
--- /dev/null
+++ b/devassist-lib/icons/severity_20/low.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/low_dark.svg b/devassist-lib/icons/severity_20/low_dark.svg
new file mode 100644
index 00000000..b4310c02
--- /dev/null
+++ b/devassist-lib/icons/severity_20/low_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/malicious.svg b/devassist-lib/icons/severity_20/malicious.svg
new file mode 100644
index 00000000..946f3889
--- /dev/null
+++ b/devassist-lib/icons/severity_20/malicious.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/malicious_dark.svg b/devassist-lib/icons/severity_20/malicious_dark.svg
new file mode 100644
index 00000000..032df876
--- /dev/null
+++ b/devassist-lib/icons/severity_20/malicious_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/medium.svg b/devassist-lib/icons/severity_20/medium.svg
new file mode 100644
index 00000000..4117ba0e
--- /dev/null
+++ b/devassist-lib/icons/severity_20/medium.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/medium_dark.svg b/devassist-lib/icons/severity_20/medium_dark.svg
new file mode 100644
index 00000000..8cd8ec41
--- /dev/null
+++ b/devassist-lib/icons/severity_20/medium_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/ok.svg b/devassist-lib/icons/severity_20/ok.svg
new file mode 100644
index 00000000..dc746080
--- /dev/null
+++ b/devassist-lib/icons/severity_20/ok.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_20/ok_dark.svg b/devassist-lib/icons/severity_20/ok_dark.svg
new file mode 100644
index 00000000..c139bab4
--- /dev/null
+++ b/devassist-lib/icons/severity_20/ok_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/critical.svg b/devassist-lib/icons/severity_24/critical.svg
new file mode 100644
index 00000000..b53aebfc
--- /dev/null
+++ b/devassist-lib/icons/severity_24/critical.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/critical_dark.svg b/devassist-lib/icons/severity_24/critical_dark.svg
new file mode 100644
index 00000000..162d5016
--- /dev/null
+++ b/devassist-lib/icons/severity_24/critical_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/high.svg b/devassist-lib/icons/severity_24/high.svg
new file mode 100644
index 00000000..50837da7
--- /dev/null
+++ b/devassist-lib/icons/severity_24/high.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/high_dark.svg b/devassist-lib/icons/severity_24/high_dark.svg
new file mode 100644
index 00000000..01ab7f2d
--- /dev/null
+++ b/devassist-lib/icons/severity_24/high_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/ignored.svg b/devassist-lib/icons/severity_24/ignored.svg
new file mode 100644
index 00000000..95180214
--- /dev/null
+++ b/devassist-lib/icons/severity_24/ignored.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/ignored_dark.svg b/devassist-lib/icons/severity_24/ignored_dark.svg
new file mode 100644
index 00000000..a8df1cee
--- /dev/null
+++ b/devassist-lib/icons/severity_24/ignored_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/low.svg b/devassist-lib/icons/severity_24/low.svg
new file mode 100644
index 00000000..a9e7b0ec
--- /dev/null
+++ b/devassist-lib/icons/severity_24/low.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/low_dark.svg b/devassist-lib/icons/severity_24/low_dark.svg
new file mode 100644
index 00000000..cfdc04b9
--- /dev/null
+++ b/devassist-lib/icons/severity_24/low_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/malicious.svg b/devassist-lib/icons/severity_24/malicious.svg
new file mode 100644
index 00000000..9c78e5cf
--- /dev/null
+++ b/devassist-lib/icons/severity_24/malicious.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/severity_24/malicious_dark.svg b/devassist-lib/icons/severity_24/malicious_dark.svg
new file mode 100644
index 00000000..5635eced
--- /dev/null
+++ b/devassist-lib/icons/severity_24/malicious_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/severity_24/medium.svg b/devassist-lib/icons/severity_24/medium.svg
new file mode 100644
index 00000000..fb1458c6
--- /dev/null
+++ b/devassist-lib/icons/severity_24/medium.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/medium_dark.svg b/devassist-lib/icons/severity_24/medium_dark.svg
new file mode 100644
index 00000000..0eb1ba32
--- /dev/null
+++ b/devassist-lib/icons/severity_24/medium_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/ok.svg b/devassist-lib/icons/severity_24/ok.svg
new file mode 100644
index 00000000..df362347
--- /dev/null
+++ b/devassist-lib/icons/severity_24/ok.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/severity_24/ok_dark.svg b/devassist-lib/icons/severity_24/ok_dark.svg
new file mode 100644
index 00000000..df362347
--- /dev/null
+++ b/devassist-lib/icons/severity_24/ok_dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/devassist-lib/icons/star-action.svg b/devassist-lib/icons/star-action.svg
new file mode 100644
index 00000000..bfc23248
--- /dev/null
+++ b/devassist-lib/icons/star-action.svg
@@ -0,0 +1,9 @@
+
diff --git a/devassist-lib/icons/tooltip/container.png b/devassist-lib/icons/tooltip/container.png
new file mode 100644
index 00000000..15333f2a
Binary files /dev/null and b/devassist-lib/icons/tooltip/container.png differ
diff --git a/devassist-lib/icons/tooltip/container_dark.png b/devassist-lib/icons/tooltip/container_dark.png
new file mode 100644
index 00000000..71980914
Binary files /dev/null and b/devassist-lib/icons/tooltip/container_dark.png differ
diff --git a/devassist-lib/icons/tooltip/critical.png b/devassist-lib/icons/tooltip/critical.png
new file mode 100644
index 00000000..5ebf58ac
Binary files /dev/null and b/devassist-lib/icons/tooltip/critical.png differ
diff --git a/devassist-lib/icons/tooltip/critical_dark.png b/devassist-lib/icons/tooltip/critical_dark.png
new file mode 100644
index 00000000..98aff91b
Binary files /dev/null and b/devassist-lib/icons/tooltip/critical_dark.png differ
diff --git a/devassist-lib/icons/tooltip/cxone_assist.png b/devassist-lib/icons/tooltip/cxone_assist.png
new file mode 100644
index 00000000..6c2c1434
Binary files /dev/null and b/devassist-lib/icons/tooltip/cxone_assist.png differ
diff --git a/devassist-lib/icons/tooltip/cxone_assist_dark.png b/devassist-lib/icons/tooltip/cxone_assist_dark.png
new file mode 100644
index 00000000..2962c7a9
Binary files /dev/null and b/devassist-lib/icons/tooltip/cxone_assist_dark.png differ
diff --git a/devassist-lib/icons/tooltip/devassist_badge.png b/devassist-lib/icons/tooltip/devassist_badge.png
new file mode 100644
index 00000000..2deaf922
Binary files /dev/null and b/devassist-lib/icons/tooltip/devassist_badge.png differ
diff --git a/devassist-lib/icons/tooltip/devassist_badge_dark.png b/devassist-lib/icons/tooltip/devassist_badge_dark.png
new file mode 100644
index 00000000..f8ee0be6
Binary files /dev/null and b/devassist-lib/icons/tooltip/devassist_badge_dark.png differ
diff --git a/devassist-lib/icons/tooltip/high.png b/devassist-lib/icons/tooltip/high.png
new file mode 100644
index 00000000..594c3ef3
Binary files /dev/null and b/devassist-lib/icons/tooltip/high.png differ
diff --git a/devassist-lib/icons/tooltip/high_dark.png b/devassist-lib/icons/tooltip/high_dark.png
new file mode 100644
index 00000000..8251b640
Binary files /dev/null and b/devassist-lib/icons/tooltip/high_dark.png differ
diff --git a/devassist-lib/icons/tooltip/low.png b/devassist-lib/icons/tooltip/low.png
new file mode 100644
index 00000000..d0f4bb3b
Binary files /dev/null and b/devassist-lib/icons/tooltip/low.png differ
diff --git a/devassist-lib/icons/tooltip/low_dark.png b/devassist-lib/icons/tooltip/low_dark.png
new file mode 100644
index 00000000..545c1765
Binary files /dev/null and b/devassist-lib/icons/tooltip/low_dark.png differ
diff --git a/devassist-lib/icons/tooltip/malicious.png b/devassist-lib/icons/tooltip/malicious.png
new file mode 100644
index 00000000..6e145e36
Binary files /dev/null and b/devassist-lib/icons/tooltip/malicious.png differ
diff --git a/devassist-lib/icons/tooltip/malicious_dark.png b/devassist-lib/icons/tooltip/malicious_dark.png
new file mode 100644
index 00000000..6e145e36
Binary files /dev/null and b/devassist-lib/icons/tooltip/malicious_dark.png differ
diff --git a/devassist-lib/icons/tooltip/medium.png b/devassist-lib/icons/tooltip/medium.png
new file mode 100644
index 00000000..ecfb6fa4
Binary files /dev/null and b/devassist-lib/icons/tooltip/medium.png differ
diff --git a/devassist-lib/icons/tooltip/medium_dark.png b/devassist-lib/icons/tooltip/medium_dark.png
new file mode 100644
index 00000000..f5e88250
Binary files /dev/null and b/devassist-lib/icons/tooltip/medium_dark.png differ
diff --git a/devassist-lib/icons/tooltip/package.png b/devassist-lib/icons/tooltip/package.png
new file mode 100644
index 00000000..ce6048f0
Binary files /dev/null and b/devassist-lib/icons/tooltip/package.png differ
diff --git a/devassist-lib/icons/tooltip/package_dark.png b/devassist-lib/icons/tooltip/package_dark.png
new file mode 100644
index 00000000..899a77f1
Binary files /dev/null and b/devassist-lib/icons/tooltip/package_dark.png differ
diff --git a/devassist-lib/icons/tooltip/severity_count/critical.png b/devassist-lib/icons/tooltip/severity_count/critical.png
new file mode 100644
index 00000000..8b8a7e56
Binary files /dev/null and b/devassist-lib/icons/tooltip/severity_count/critical.png differ
diff --git a/devassist-lib/icons/tooltip/severity_count/critical_dark.png b/devassist-lib/icons/tooltip/severity_count/critical_dark.png
new file mode 100644
index 00000000..c743dd71
Binary files /dev/null and b/devassist-lib/icons/tooltip/severity_count/critical_dark.png differ
diff --git a/devassist-lib/icons/tooltip/severity_count/high.png b/devassist-lib/icons/tooltip/severity_count/high.png
new file mode 100644
index 00000000..fc36e929
Binary files /dev/null and b/devassist-lib/icons/tooltip/severity_count/high.png differ
diff --git a/devassist-lib/icons/tooltip/severity_count/high_dark.png b/devassist-lib/icons/tooltip/severity_count/high_dark.png
new file mode 100644
index 00000000..4ac2fc36
Binary files /dev/null and b/devassist-lib/icons/tooltip/severity_count/high_dark.png differ
diff --git a/devassist-lib/icons/tooltip/severity_count/low.png b/devassist-lib/icons/tooltip/severity_count/low.png
new file mode 100644
index 00000000..a0574bce
Binary files /dev/null and b/devassist-lib/icons/tooltip/severity_count/low.png differ
diff --git a/devassist-lib/icons/tooltip/severity_count/low_dark.png b/devassist-lib/icons/tooltip/severity_count/low_dark.png
new file mode 100644
index 00000000..81df61cf
Binary files /dev/null and b/devassist-lib/icons/tooltip/severity_count/low_dark.png differ
diff --git a/devassist-lib/icons/tooltip/severity_count/medium.png b/devassist-lib/icons/tooltip/severity_count/medium.png
new file mode 100644
index 00000000..c0b5679f
Binary files /dev/null and b/devassist-lib/icons/tooltip/severity_count/medium.png differ
diff --git a/devassist-lib/icons/tooltip/severity_count/medium_dark.png b/devassist-lib/icons/tooltip/severity_count/medium_dark.png
new file mode 100644
index 00000000..296a081b
Binary files /dev/null and b/devassist-lib/icons/tooltip/severity_count/medium_dark.png differ
diff --git a/devassist-lib/icons/unknown_16.svg b/devassist-lib/icons/unknown_16.svg
new file mode 100644
index 00000000..d63f29bf
--- /dev/null
+++ b/devassist-lib/icons/unknown_16.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/unknown_16_dark.svg b/devassist-lib/icons/unknown_16_dark.svg
new file mode 100644
index 00000000..a5270a2a
--- /dev/null
+++ b/devassist-lib/icons/unknown_16_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/devassist-lib/icons/welcomePageScanner.svg b/devassist-lib/icons/welcomePageScanner.svg
new file mode 100644
index 00000000..e84b61ee
--- /dev/null
+++ b/devassist-lib/icons/welcomePageScanner.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/devassist-lib/icons/welcomePageScanner_dark.svg b/devassist-lib/icons/welcomePageScanner_dark.svg
new file mode 100644
index 00000000..798ceb92
--- /dev/null
+++ b/devassist-lib/icons/welcomePageScanner_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/devassist-lib/plugin.xml b/devassist-lib/plugin.xml
new file mode 100644
index 00000000..d2c221b1
--- /dev/null
+++ b/devassist-lib/plugin.xml
@@ -0,0 +1,332 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/devassist-lib/pom.xml b/devassist-lib/pom.xml
new file mode 100644
index 00000000..42f73309
--- /dev/null
+++ b/devassist-lib/pom.xml
@@ -0,0 +1,12 @@
+
+
+ 4.0.0
+
+ com.checkmarx.ast.eclipse
+ checkmarx-eclipse-plugin
+ 1.0.0-SNAPSHOT
+
+ com.checkmarx.eclipse.devassist
+ eclipse-plugin
+
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/Activator.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/Activator.java
new file mode 100644
index 00000000..c52616db
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/Activator.java
@@ -0,0 +1,84 @@
+package com.checkmarx.eclipse.devassist;
+
+import org.eclipse.core.runtime.Plugin;
+import org.osgi.framework.BundleContext;
+
+import com.checkmarx.eclipse.common.preferences.Preferences;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.devassist.backend.GlobalScannerController;
+import com.checkmarx.eclipse.devassist.backend.ScannerPreferencesListener;
+import com.checkmarx.eclipse.devassist.configuration.McpInstallService;
+
+/**
+ * Devassist library activator.
+ * Initializes McpInstallService to register authentication handlers.
+ */
+public class Activator extends Plugin {
+
+ public static final String PLUGIN_ID = "com.checkmarx.eclipse.devassist";
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+
+ try {
+ // Step 1: Register scanner preferences listener
+ // Bridges CheckmarxPreferencePage changes to GlobalScannerController
+ ScannerPreferencesListener preferencesListener = new ScannerPreferencesListener();
+ Preferences.addSettingsChangeNotifier(preferencesListener);
+ CxLogger.info("[DEVASSIST] Registered ScannerPreferencesListener");
+
+ // Step 2: Initialize GlobalScannerController with current preferences
+ // Ensures scanner execution guards use latest stored preferences
+ GlobalScannerController controller = GlobalScannerController.getInstance();
+
+ // Load preferences from store and sync with controller
+ boolean ascaEnabled = Preferences.STORE.getBoolean(Preferences.PREF_ASCA_ENABLED);
+ boolean ossEnabled = Preferences.STORE.getBoolean(Preferences.PREF_OSS_ENABLED);
+ boolean secretsEnabled = Preferences.STORE.getBoolean(Preferences.PREF_SECRETS_ENABLED);
+ boolean containersEnabled = Preferences.STORE.getBoolean(Preferences.PREF_CONTAINERS_ENABLED);
+ boolean iacEnabled = Preferences.STORE.getBoolean(Preferences.PREF_IAC_ENABLED);
+
+ CxLogger.info("[ACTIVATOR] Initial preferences loaded: ASCA=" + ascaEnabled + ", OSS=" + ossEnabled +
+ ", SECRETS=" + secretsEnabled + ", CONTAINERS=" + containersEnabled + ", IAC=" + iacEnabled);
+
+ // Sync preferences to controller (mirrors JetBrains initialization)
+ if (ascaEnabled) controller.enableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.ASCA);
+ else controller.disableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.ASCA);
+
+ if (ossEnabled) controller.enableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.OSS);
+ else controller.disableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.OSS);
+
+ if (secretsEnabled) controller.enableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.SECRETS);
+ else controller.disableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.SECRETS);
+
+ if (containersEnabled) controller.enableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.CONTAINERS);
+ else controller.disableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.CONTAINERS);
+
+ if (iacEnabled) controller.enableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.IAC);
+ else controller.disableScanner(com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType.IAC);
+
+ CxLogger.info("[DEVASSIST] Initialized GlobalScannerController with preferences. " +
+ controller.getStateReport());
+
+ } catch (Exception e) {
+ CxLogger.error("[DEVASSIST] Error during initialization: " + e.getMessage(), e);
+ }
+
+ try {
+ // Step 3: Register authentication handlers (existing code)
+ // Calling a real static member (not just the .class literal) is what forces the JVM
+ // to run McpInstallService's static initializer, which registers the auth handlers.
+ // This also does its documented job: auto-install MCP if already authenticated.
+ McpInstallService.attemptAutoInstall();
+ CxLogger.info("[DEVASSIST] Initialized authentication handlers");
+ } catch (Exception e) {
+ CxLogger.error("[DEVASSIST] Error registering authentication handlers: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ super.stop(context);
+ }
+}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/AuthenticationStateListener.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/AuthenticationStateListener.java
new file mode 100644
index 00000000..0b96fbc5
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/AuthenticationStateListener.java
@@ -0,0 +1,92 @@
+package com.checkmarx.eclipse.devassist.backend;
+
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+
+import com.checkmarx.eclipse.common.preferences.Preferences;
+import com.checkmarx.eclipse.common.listener.IWorkspaceScanService;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType;
+import java.util.EnumSet;
+
+/**
+ * Listens for authentication state changes (CREDENTIALS_VALIDATED flag) and triggers
+ * workspace scan when user logs in.
+ *
+ * Problem it solves:
+ * - When user logs in, no preferences change, so ScannerPreferencesListener doesn't trigger scan
+ * - But we still need to scan projects that were opened before authentication
+ *
+ * Solution:
+ * - Listen to CREDENTIALS_VALIDATED changes
+ * - When it becomes true (login), trigger workspace scan immediately
+ * - ScannerPreferencesListener handles preference changes separately
+ */
+public class AuthenticationStateListener implements IPropertyChangeListener {
+
+ private static final String LOG_TAG = "[AUTH-STATE-LISTENER]";
+
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ if (event == null || event.getProperty() == null) {
+ return;
+ }
+
+ // Only respond to authentication state changes
+ if (!Preferences.CREDENTIALS_VALIDATED.equals(event.getProperty())) {
+ return;
+ }
+
+ Object newValue = event.getNewValue();
+ boolean nowAuthenticated = newValue instanceof Boolean && (Boolean) newValue;
+
+ if (nowAuthenticated) {
+ // Handle login: clear scan cache and trigger workspace scan
+ handleLogin();
+ } else {
+ // Handle logout: remove MCP server configuration
+ handleLogout();
+ }
+ }
+
+ private void handleLogin() {
+ CxLogger.info(LOG_TAG + " User authenticated - clearing scan cache and triggering workspace scan...");
+
+ // CRITICAL: Clear scan state cache so files that were never scanned (before authentication)
+ // are not treated as "unchanged" and skipped. Without this, files show as "cached/unchanged"
+ // and the scan is skipped even though they were never actually scanned before.
+ try {
+ CxLogger.info(LOG_TAG + " Clearing scan state cache for all scanners...");
+ EnumSet allScanners = EnumSet.allOf(ScannerType.class);
+ ScanStateCacheClearer.clearForScanners(allScanners);
+ CxLogger.info(LOG_TAG + " ✓ Scan cache cleared");
+ } catch (Exception e) {
+ CxLogger.warning(LOG_TAG + " Error clearing scan cache: " + e.getMessage());
+ }
+
+ // Now trigger workspace scan with cleared cache
+ IWorkspaceScanService scanService = Preferences.getWorkspaceScanService();
+ if (scanService != null) {
+ try {
+ scanService.scanWorkspace();
+ CxLogger.info(LOG_TAG + " ✓ Workspace scan triggered on login");
+ } catch (Exception e) {
+ CxLogger.error(LOG_TAG + " Error triggering workspace scan: " + e.getMessage(), e);
+ }
+ } else {
+ CxLogger.warning(LOG_TAG + " Workspace scan service not available");
+ }
+ }
+
+ private void handleLogout() {
+ CxLogger.info(LOG_TAG + " User logged out - removing MCP server configuration...");
+
+ try {
+ // Import statement needed: com.checkmarx.eclipse.devassist.configuration.McpInstallService
+ com.checkmarx.eclipse.devassist.configuration.McpInstallService.uninstall();
+ CxLogger.info(LOG_TAG + " ✓ MCP server configuration removed on logout");
+ } catch (Exception e) {
+ CxLogger.error(LOG_TAG + " Error removing MCP configuration on logout: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/Constants.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/Constants.java
new file mode 100644
index 00000000..a57aefe9
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/Constants.java
@@ -0,0 +1,31 @@
+package com.checkmarx.eclipse.devassist.backend;
+
+/**
+ * Constants for DevAssist backend operations.
+ * Mirrors JetBrains Constants pattern.
+ */
+public class Constants {
+ // Main plugin bundle id, used to load icons/resources that live in the main plugin bundle
+ public static final String MAIN_PLUGIN_ID = "com.checkmarx.eclipse.plugin";
+
+ // UI strings
+ public static final String BTN_OPEN_SETTINGS = "Open Settings";
+ public static final String FINDINGS_PROMO_DESCRIPTION = "Checkmarx Developer Assist stops vulnerabilities where your code is written, with fixes you can actually trust.";
+
+ // Log messages
+ public static final String ERROR_BUILDING_CX_WRAPPER = "An error occurred while instantiating a CxWrapper: %s";
+
+ // Severity level string constants
+ public static final String MALICIOUS_SEVERITY = "Malicious";
+ public static final String CRITICAL_SEVERITY = "Critical";
+ public static final String HIGH_SEVERITY = "High";
+ public static final String MEDIUM_SEVERITY = "Medium";
+ public static final String LOW_SEVERITY = "Low";
+ public static final String OK = "OK";
+ public static final String UNKNOWN = "Unknown";
+ public static final String IGNORE_LABEL = "Ignored";
+
+ private Constants() {
+ // Private constructor to prevent instantiation
+ }
+}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/DevAssistScanStateHolder.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/DevAssistScanStateHolder.java
new file mode 100644
index 00000000..503d066f
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/DevAssistScanStateHolder.java
@@ -0,0 +1,244 @@
+package com.checkmarx.eclipse.devassist.backend;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.security.MessageDigest;
+
+import com.checkmarx.eclipse.common.utils.CxLogger;
+
+/**
+ * Tracks file modification state to prevent redundant scans.
+ *
+ * Stores a composite "state hash" for each file:
+ * - Document modification timestamp
+ * - File system last-modified time
+ * - Editor content hash
+ *
+ * When a file is requested for scanning, we compare the current state
+ * with the cached state. If unchanged, we skip the scan and return cached results.
+ *
+ * This mirrors the JetBrains DevAssistScanStateHolder pattern.
+ */
+public class DevAssistScanStateHolder {
+
+ private static final String LOG_TAG = "[SCAN-STATE]";
+
+ private final ConcurrentHashMap fileStateHash = new ConcurrentHashMap<>();
+ // Atomic in-flight marker to prevent concurrent scans of the same file
+ // putIfAbsent() detects if another thread is already scanning this file
+ private final ConcurrentHashMap inFlightScans = new ConcurrentHashMap<>();
+
+ /**
+ * Get the cached state hash for a file.
+ *
+ * @param filePath Absolute file path
+ * @return Last recorded state hash, or null if never scanned
+ */
+ public Long getStateHash(String filePath) {
+ if (filePath == null) {
+ return null;
+ }
+ return fileStateHash.get(filePath);
+ }
+
+ /**
+ * Update the state hash for a file (after successful scan).
+ *
+ * @param filePath Absolute file path
+ * @param stateHash New state hash
+ */
+ public void updateStateHash(String filePath, long stateHash) {
+ if (filePath == null) {
+ return;
+ }
+
+ Long previous = fileStateHash.put(filePath, stateHash);
+ CxLogger.info(LOG_TAG + " Updated state hash for: " + filePath +
+ " (previous: " + previous + ", new: " + stateHash + ")");
+ }
+
+ /**
+ * Check if a file has changed since last scan AND mark it as in-flight.
+ * CRITICAL: Uses atomic putIfAbsent() to prevent concurrent scans of the same file.
+ * If another thread is already scanning this file, returns false to skip duplicate work.
+ *
+ * @param filePath Absolute file path
+ * @param currentStateHash Current state of the file
+ * @return true if file changed AND no other scan is in-flight, false otherwise
+ */
+ public boolean hasChanged(String filePath, long currentStateHash) {
+ if (filePath == null) {
+ return true;
+ }
+
+ Long cachedHash = fileStateHash.get(filePath);
+
+ // Never scanned before
+ if (cachedHash == null) {
+ CxLogger.info(LOG_TAG + " File never scanned: " + filePath);
+ // Atomic check: if another thread beat us here, skip to avoid duplicate work
+ if (inFlightScans.putIfAbsent(filePath, true) != null) {
+ CxLogger.info(LOG_TAG + " BLOCKED: Another scan already in-flight for: " + filePath);
+ return false;
+ }
+ return true;
+ }
+
+ // Compare hashes
+ boolean changed = !cachedHash.equals(currentStateHash);
+ if (!changed) {
+ CxLogger.info(LOG_TAG + " File unchanged (cached): " + filePath);
+ return false;
+ }
+
+ // File changed - atomically mark as in-flight to prevent duplicate concurrent scans
+ if (inFlightScans.putIfAbsent(filePath, true) != null) {
+ CxLogger.info(LOG_TAG + " BLOCKED: Another scan already in-flight for: " + filePath);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Mark a file scan as complete (remove in-flight marker).
+ * MUST be called after scan completes to unblock other threads.
+ *
+ * @param filePath Absolute file path
+ */
+ public void markScanComplete(String filePath) {
+ if (filePath == null) {
+ return;
+ }
+ inFlightScans.remove(filePath);
+ }
+
+ /**
+ * Clear state for a specific file (e.g., when file is deleted).
+ * Also clears any in-flight scan marker.
+ *
+ * @param filePath Absolute file path
+ */
+ public void clearFileState(String filePath) {
+ if (filePath == null) {
+ return;
+ }
+
+ fileStateHash.remove(filePath);
+ inFlightScans.remove(filePath);
+ CxLogger.info(LOG_TAG + " Cleared state for: " + filePath);
+ }
+
+ /**
+ * Clear all state (on project close).
+ * Also clears all in-flight scan markers.
+ */
+ public void clearAll() {
+ fileStateHash.clear();
+ inFlightScans.clear();
+ CxLogger.info(LOG_TAG + " All state cleared");
+ }
+
+ /**
+ * Compute a state hash for a file based on:
+ * - File system last modified time
+ * - Document content hash (if open in editor with unsaved changes)
+ *
+ * CRITICAL FIX: When file is dirty (unsaved), hash actual document content instead of
+ * using System.nanoTime(). Previous implementation returned different hash on every call,
+ * causing unnecessary rescans even when content didn't change.
+ *
+ * @param filePath File to hash
+ * @return Composite state hash
+ */
+ public static long computeFileStateHash(String filePath) {
+ try {
+ java.nio.file.Path path = java.nio.file.Paths.get(filePath);
+ long fileModified = java.nio.file.Files.getLastModifiedTime(path).toMillis();
+
+ // Check if file is open in editor with unsaved changes
+ // If dirty (unsaved), hash actual document content to detect real changes
+ String dirtyDocumentContent = null;
+ try {
+ org.eclipse.ui.IWorkbench workbench = org.eclipse.ui.PlatformUI.getWorkbench();
+ if (workbench != null && !workbench.isClosing()) {
+ for (org.eclipse.ui.IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
+ for (org.eclipse.ui.IWorkbenchPage page : window.getPages()) {
+ for (org.eclipse.ui.IEditorReference ref : page.getEditorReferences()) {
+ org.eclipse.ui.IEditorPart editor = ref.getEditor(false);
+ if (editor != null && editor.isDirty()) {
+ try {
+ String editorPath = editor.getEditorInput().getAdapter(org.eclipse.core.resources.IFile.class)
+ .getLocation().toOSString();
+ if (editorPath.equals(filePath)) {
+ // Get document content from editor
+ if (editor instanceof org.eclipse.ui.texteditor.ITextEditor) {
+ org.eclipse.ui.texteditor.ITextEditor textEditor = (org.eclipse.ui.texteditor.ITextEditor) editor;
+ org.eclipse.jface.text.IDocument doc = textEditor.getDocumentProvider().getDocument(editor.getEditorInput());
+ if (doc != null) {
+ dirtyDocumentContent = doc.get();
+ break;
+ }
+ }
+ }
+ } catch (Exception e2) {
+ // Skip if we can't get editor or document
+ }
+ }
+ }
+ if (dirtyDocumentContent != null) break;
+ }
+ if (dirtyDocumentContent != null) break;
+ }
+ }
+ } catch (Exception e) {
+ // If workbench check fails, just use file timestamp
+ dirtyDocumentContent = null;
+ }
+
+ // If file has unsaved changes, hash actual document content
+ // This ensures same content hashes to same value (no unnecessary rescans)
+ if (dirtyDocumentContent != null) {
+ return hashDocumentContent(dirtyDocumentContent);
+ }
+
+ return fileModified;
+ } catch (Exception e) {
+ CxLogger.warning(LOG_TAG + " Error computing state hash: " + e.getMessage());
+ return System.currentTimeMillis();
+ }
+ }
+
+ /**
+ * Compute SHA-256 hash of document content.
+ * CRITICAL: Enables stable hashing of dirty files - same content always produces same hash.
+ *
+ * @param content Document text content
+ * @return Long hash value (first 8 bytes of SHA-256)
+ */
+ private static long hashDocumentContent(String content) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ byte[] hash = md.digest(content.getBytes("UTF-8"));
+ // Convert first 8 bytes to long
+ long result = 0;
+ for (int i = 0; i < 8; i++) {
+ result = (result << 8) | (hash[i] & 0xFF);
+ }
+ return result;
+ } catch (Exception e) {
+ CxLogger.warning(LOG_TAG + " Error hashing document content: " + e.getMessage());
+ // Fallback to content length + hash code
+ return ((long) content.length() << 32) | (content.hashCode() & 0xFFFFFFFFL);
+ }
+ }
+
+ /**
+ * Get statistics about tracked files.
+ *
+ * @return Summary string
+ */
+ public String getStatistics() {
+ return "Tracked files: " + fileStateHash.size();
+ }
+}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/GlobalScannerController.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/GlobalScannerController.java
new file mode 100644
index 00000000..42a8d90b
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/GlobalScannerController.java
@@ -0,0 +1,213 @@
+package com.checkmarx.eclipse.devassist.backend;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+
+/**
+ * Application-level singleton managing global scanner state.
+ *
+ * Responsibilities:
+ * - Track which scanners are enabled/disabled globally
+ * - Sync with user preferences/settings
+ * - Notify all open projects when scanner state changes
+ * - Provide query methods for scanner availability
+ *
+ * This is an application-scoped service (one instance for entire Eclipse).
+ * Each project's ScannerRegistry checks this controller before executing scans.
+ *
+ * Mirrors the JetBrains GlobalScannerController pattern.
+ */
+public class GlobalScannerController {
+
+ private static final String LOG_TAG = "[GLOBAL-SCANNER]";
+ private static GlobalScannerController instance;
+
+ // Global enable/disable state for each scanner
+ private final ConcurrentHashMap scannerState = new ConcurrentHashMap<>();
+
+ // Listeners notified when scanner state changes
+ // Using CopyOnWriteArrayList for thread-safe concurrent iteration and mutation
+ private final List stateListeners = new CopyOnWriteArrayList<>();
+
+ /**
+ * Get the global singleton instance.
+ * Lazily creates on first access.
+ *
+ * @return Global scanner controller
+ */
+ public synchronized static GlobalScannerController getInstance() {
+ if (instance == null) {
+ instance = new GlobalScannerController();
+ }
+ return instance;
+ }
+
+ /**
+ * Enable a scanner globally.
+ *
+ * @param type Scanner type to enable
+ */
+ public void enableScanner(ScannerType type) {
+ if (type == null) {
+ return;
+ }
+
+ boolean wasDisabled = Boolean.FALSE.equals(scannerState.put(type, true));
+
+ if (wasDisabled) {
+ CxLogger.info(LOG_TAG + " Enabled scanner: " + type.getDisplayName());
+ notifyScannerStateChanged(type, true);
+ }
+ }
+
+ /**
+ * Disable a scanner globally.
+ *
+ * @param type Scanner type to disable
+ */
+ public void disableScanner(ScannerType type) {
+ if (type == null) {
+ return;
+ }
+
+ boolean wasEnabled = Boolean.TRUE.equals(scannerState.put(type, false));
+
+ if (wasEnabled) {
+ CxLogger.info(LOG_TAG + " Disabled scanner: " + type.getDisplayName());
+ notifyScannerStateChanged(type, false);
+ }
+ }
+
+ /**
+ * Check if a scanner is enabled globally.
+ *
+ * @param type Scanner type to check
+ * @return true if enabled, false if disabled
+ */
+ public boolean isScannerEnabled(ScannerType type) {
+ if (type == null) {
+ return false;
+ }
+
+ // Default to enabled if not explicitly set
+ return scannerState.getOrDefault(type, true);
+ }
+
+ /**
+ * Enable all scanners.
+ */
+ public void enableAllScanners() {
+ CxLogger.info(LOG_TAG + " Enabling all scanners");
+
+ for (ScannerType type : ScannerType.values()) {
+ enableScanner(type);
+ }
+ }
+
+ /**
+ * Disable all scanners.
+ */
+ public void disableAllScanners() {
+ CxLogger.info(LOG_TAG + " Disabling all scanners");
+
+ for (ScannerType type : ScannerType.values()) {
+ disableScanner(type);
+ }
+ }
+
+ /**
+ * Get count of enabled scanners.
+ *
+ * @return Number of enabled scanners
+ */
+ public int getEnabledScannerCount() {
+ int count = 0;
+ for (ScannerType type : ScannerType.values()) {
+ if (isScannerEnabled(type)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Register a listener to be notified of state changes.
+ * Thread-safe: can be called concurrently with notifications.
+ *
+ * @param listener Listener callback
+ */
+ public void addScannerStateListener(ScannerStateListener listener) {
+ if (listener != null) {
+ stateListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregister a state listener.
+ * Thread-safe: can be called concurrently with notifications.
+ *
+ * @param listener Listener to remove
+ */
+ public void removeScannerStateListener(ScannerStateListener listener) {
+ if (listener != null) {
+ stateListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Notify all listeners of a scanner state change.
+ * Thread-safe: listeners can register/unregister concurrently without
+ * ConcurrentModificationException.
+ *
+ * @param type Changed scanner type
+ * @param enabled New enabled state
+ */
+ private void notifyScannerStateChanged(ScannerType type, boolean enabled) {
+ for (ScannerStateListener listener : stateListeners) {
+ try {
+ listener.onScannerStateChanged(type, enabled);
+ } catch (Exception e) {
+ CxLogger.warning(LOG_TAG + " Error notifying listener: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Get a detailed state report.
+ *
+ * @return Multi-line status string
+ */
+ public String getStateReport() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(LOG_TAG).append(" Scanner State Report:\n");
+
+ for (ScannerType type : ScannerType.values()) {
+ boolean enabled = isScannerEnabled(type);
+ sb.append(" ").append(type.getDisplayName()).append(": ")
+ .append(enabled ? "ENABLED" : "DISABLED").append("\n");
+ }
+
+ sb.append(" Total Enabled: ").append(getEnabledScannerCount()).append("/")
+ .append(ScannerType.values().length);
+
+ return sb.toString();
+ }
+
+ /**
+ * Listener interface for scanner state changes.
+ * Implemented by project registries to react to global changes.
+ */
+ public interface ScannerStateListener {
+ /**
+ * Called when a scanner's enabled state changes globally.
+ *
+ * @param type Changed scanner type
+ * @param enabled New enabled state
+ */
+ void onScannerStateChanged(ScannerType type, boolean enabled);
+ }
+}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScanStateCacheClearer.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScanStateCacheClearer.java
new file mode 100644
index 00000000..1880325d
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScanStateCacheClearer.java
@@ -0,0 +1,66 @@
+package com.checkmarx.eclipse.devassist.backend;
+
+import java.util.Set;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.QualifiedName;
+
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType;
+
+/**
+ * Clears file state cache for scanners that are being re-enabled.
+ *
+ * When a workspace scanner (OSS, IaC, Container) is disabled, findings are purged.
+ * When re-enabled, the state cache (in DevAssistScanStateHolder) still holds old
+ * file hashes, preventing fresh scans. This clears the cache for those scanners
+ * so manifest files get re-scanned immediately.
+ *
+ * Mirrors ScannerMarkerPurger pattern for disabled scanners.
+ */
+public class ScanStateCacheClearer {
+
+ private static final String LOG_TAG = "[SCAN-STATE-CACHE-CLEARER]";
+ private static final String PLUGIN_ID = "com.checkmarx.eclipse.plugin";
+ private static final QualifiedName STATE_HOLDER_KEY =
+ new QualifiedName(PLUGIN_ID, "state-holder");
+
+ private ScanStateCacheClearer() {
+ }
+
+ /**
+ * Clear state cache for scanners that are being re-enabled.
+ * Allows manifest files to be re-scanned even if content hasn't changed.
+ *
+ * @param newlyEnabledScanners Scanners that just transitioned from disabled to enabled
+ */
+ public static void clearForScanners(Set newlyEnabledScanners) {
+ if (newlyEnabledScanners == null || newlyEnabledScanners.isEmpty()) {
+ return;
+ }
+
+ for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
+ if (!project.isOpen()) {
+ continue;
+ }
+ try {
+ DevAssistScanStateHolder stateHolder =
+ (DevAssistScanStateHolder) project.getSessionProperty(STATE_HOLDER_KEY);
+ if (stateHolder == null) {
+ continue;
+ }
+
+ // Clear ALL state cache entries to force fresh scans
+ // This ensures manifest files are re-scanned regardless of whether
+ // their content changed, since scanner enablement counts as "state changed"
+ stateHolder.clearAll();
+
+ CxLogger.info(LOG_TAG + " Cleared state cache for project: " + project.getName());
+ } catch (Exception e) {
+ CxLogger.warning(LOG_TAG + " Error clearing state cache for project " +
+ project.getName() + ": " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerMarkerPurger.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerMarkerPurger.java
new file mode 100644
index 00000000..17950c74
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerMarkerPurger.java
@@ -0,0 +1,111 @@
+package com.checkmarx.eclipse.devassist.backend;
+
+import java.util.List;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType;
+import com.checkmarx.eclipse.devassist.problems.ProblemDecorator;
+import com.checkmarx.eclipse.devassist.problems.ProblemHolderService;
+import com.checkmarx.eclipse.devassist.ui.findings.marker.MarkerIssueMapper;
+
+/**
+ * Purges findings for a scanner that has just been disabled.
+ *
+ * When a scanner is disabled, ScannerFactory stops running it for future scans, but
+ * results it already produced (cached ScanIssues, editor decorations, and IMarkers)
+ * remain until something removes them. This purges all three, workspace-wide, so a
+ * disabled scanner's findings disappear immediately.
+ */
+public class ScannerMarkerPurger {
+
+ private static final String LOG_TAG = "[SCANNER-MARKER-PURGER]";
+ private static final String MARKER_TYPE = "com.checkmarx.eclipse.plugin.checkmarxProblemMarker";
+ private static final String PLUGIN_ID = "com.checkmarx.eclipse.plugin";
+ private static final QualifiedName PROBLEM_HOLDER_KEY = new QualifiedName(PLUGIN_ID, "problem-holder");
+
+ private ScannerMarkerPurger() {
+ }
+
+ /**
+ * Remove all markers, cached issues, and editor decorations produced by the given
+ * scanner, across every open project in the workspace.
+ *
+ * @param type Scanner type that was just disabled
+ */
+ public static void purgeScanner(ScannerType type) {
+ if (type == null) {
+ return;
+ }
+
+ String scannerName = type.name();
+ purgeMarkers(scannerName);
+ purgeCacheAndDecorations(scannerName);
+ }
+
+ private static void purgeMarkers(String scannerName) {
+ try {
+ IMarker[] markers = ResourcesPlugin.getWorkspace().getRoot()
+ .findMarkers(MARKER_TYPE, true, IResource.DEPTH_INFINITE);
+ int deleted = 0;
+ for (IMarker marker : markers) {
+ String engine = marker.getAttribute(MarkerIssueMapper.ATTR_SCAN_ENGINE, null);
+ if (scannerName.equals(engine)) {
+ marker.delete();
+ deleted++;
+ }
+ }
+ CxLogger.info(LOG_TAG + " Deleted " + deleted + " markers for scanner: " + scannerName);
+ } catch (CoreException e) {
+ CxLogger.error(LOG_TAG + " Error deleting markers for scanner " + scannerName + ": " + e.getMessage(), e);
+ }
+ }
+
+ private static void purgeCacheAndDecorations(String scannerName) {
+ for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
+ if (!project.isOpen()) {
+ continue;
+ }
+ try {
+ ProblemHolderService problemHolder = (ProblemHolderService) project.getSessionProperty(PROBLEM_HOLDER_KEY);
+ if (problemHolder == null) {
+ continue;
+ }
+
+ List affectedFiles = problemHolder.removeAllIssuesForScanner(scannerName);
+ for (String filePath : affectedFiles) {
+ IFile[] files = ResourcesPlugin.getWorkspace().getRoot()
+ .findFilesForLocation(org.eclipse.core.runtime.Path.fromOSString(filePath));
+ IFile file = (files != null && files.length > 0) ? files[0] : null;
+ if (file != null) {
+ List remaining =
+ problemHolder.getScanIssuesByFile(filePath);
+ // Filter to only exclude ignored issues - include OK/UNKNOWN gutter icons
+ com.checkmarx.eclipse.devassist.ignore.IgnoreManager ignoreManager =
+ com.checkmarx.eclipse.devassist.ignore.IgnoreManager.getInstance(project);
+ java.util.List activeIssues =
+ new java.util.ArrayList<>();
+ for (com.checkmarx.eclipse.devassist.model.ScanIssue issue : remaining) {
+ if (issue == null) {
+ continue;
+ }
+ if (!ignoreManager.isIgnored(issue)) {
+ activeIssues.add(issue);
+ }
+ }
+ ProblemDecorator.decorateEditor(file, activeIssues);
+ }
+ }
+ } catch (Exception e) {
+ CxLogger.warning(LOG_TAG + " Error purging cache for project " + project.getName() + ": " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerPreferencesListener.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerPreferencesListener.java
new file mode 100644
index 00000000..22e83f7a
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerPreferencesListener.java
@@ -0,0 +1,108 @@
+package com.checkmarx.eclipse.devassist.backend;
+
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.checkmarx.eclipse.common.listener.ISettingsChangeNotifier;
+import com.checkmarx.eclipse.common.listener.IWorkspaceScanService;
+import com.checkmarx.eclipse.common.preferences.Preferences;
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.devassist.backend.ScannerRegistry.ScannerType;
+
+/**
+ * Listens for preference changes and syncs them to GlobalScannerController.
+ *
+ * This bridges the gap between CheckmarxPreferencePage (in common-lib)
+ * and GlobalScannerController (in devassist-lib) using the listener pattern
+ * to avoid circular module dependencies.
+ *
+ * Pattern from JetBrains: When preferences change, listeners update the
+ * runtime controller state so that scanner execution is gated by the
+ * latest preferences.
+ *
+ * Lifecycle:
+ * 1. User changes scanner checkboxes in CheckmarxPreferencePage
+ * 2. CheckmarxPreferencePage.performOk() saves to preferences
+ * 3. CheckmarxPreferencePage notifies ISettingsChangeNotifier
+ * 4. This listener's onSettingsApplied() is called
+ * 5. GlobalScannerController is synced with new preferences
+ * 6. Future scans respect the new preferences
+ */
+public class ScannerPreferencesListener implements ISettingsChangeNotifier {
+
+ private static final String LOG_TAG = "[SCANNER-PREFS-LISTENER]";
+
+ /**
+ * Called when preferences are applied (from CheckmarxPreferencePage.performOk()).
+ * Syncs the preference store with GlobalScannerController so execution guards use latest state,
+ * then reacts to whatever changed:
+ * - Scanners that just got disabled have their existing findings purged immediately.
+ * - Scanners that just got enabled are combined into a single consolidated scan trigger,
+ * even if several scanners were toggled on at once in the same Apply/OK click.
+ */
+ @Override
+ public void notifySettingsApplied() {
+ try {
+ CxLogger.info(LOG_TAG + " Syncing preferences to GlobalScannerController");
+
+ GlobalScannerController controller = GlobalScannerController.getInstance();
+
+ Map desiredState = new EnumMap<>(ScannerType.class);
+ desiredState.put(ScannerType.ASCA, Preferences.STORE.getBoolean(Preferences.PREF_ASCA_ENABLED));
+ desiredState.put(ScannerType.OSS, Preferences.STORE.getBoolean(Preferences.PREF_OSS_ENABLED));
+ desiredState.put(ScannerType.SECRETS, Preferences.STORE.getBoolean(Preferences.PREF_SECRETS_ENABLED));
+ desiredState.put(ScannerType.CONTAINERS, Preferences.STORE.getBoolean(Preferences.PREF_CONTAINERS_ENABLED));
+ desiredState.put(ScannerType.IAC, Preferences.STORE.getBoolean(Preferences.PREF_IAC_ENABLED));
+
+ CxLogger.info(LOG_TAG + " Read from STORE: " + desiredState);
+
+ Set newlyEnabled = EnumSet.noneOf(ScannerType.class);
+ Set newlyDisabled = EnumSet.noneOf(ScannerType.class);
+
+ for (Map.Entry entry : desiredState.entrySet()) {
+ ScannerType type = entry.getKey();
+ boolean shouldBeEnabled = entry.getValue();
+ boolean wasEnabled = controller.isScannerEnabled(type);
+
+ if (shouldBeEnabled) {
+ controller.enableScanner(type);
+ } else {
+ controller.disableScanner(type);
+ }
+
+ if (shouldBeEnabled && !wasEnabled) {
+ newlyEnabled.add(type);
+ } else if (!shouldBeEnabled && wasEnabled) {
+ newlyDisabled.add(type);
+ }
+ }
+
+ CxLogger.info(LOG_TAG + " Preference sync complete. " + controller.getStateReport());
+
+ // Disable: purge findings for scanners that just got turned off.
+ for (ScannerType type : newlyDisabled) {
+ CxLogger.info(LOG_TAG + " Purging findings for disabled scanner: " + type);
+ ScannerMarkerPurger.purgeScanner(type);
+ }
+
+ // Enable (single or multiple at once): clear state cache and trigger one consolidated scan.
+ if (!newlyEnabled.isEmpty()) {
+ CxLogger.info(LOG_TAG + " Clearing state cache for newly enabled scanners: " + newlyEnabled);
+ ScanStateCacheClearer.clearForScanners(newlyEnabled);
+
+ CxLogger.info(LOG_TAG + " Triggering consolidated scan for newly enabled scanners: " + newlyEnabled);
+ IWorkspaceScanService scanService = Preferences.getWorkspaceScanService();
+ if (scanService != null) {
+ scanService.scanWorkspace();
+ } else {
+ CxLogger.warning(LOG_TAG + " No workspace scan service registered; cannot trigger scan");
+ }
+ }
+
+ } catch (Exception e) {
+ CxLogger.error(LOG_TAG + " Failed to sync preferences: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerRegistry.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerRegistry.java
new file mode 100644
index 00000000..34206253
--- /dev/null
+++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/backend/ScannerRegistry.java
@@ -0,0 +1,428 @@
+package com.checkmarx.eclipse.devassist.backend;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.core.resources.IProject;
+
+import com.checkmarx.eclipse.common.utils.CxLogger;
+import com.checkmarx.eclipse.devassist.basescanner.ScannerService;
+
+/**
+ * Manages the lifecycle of scanner services for a project.
+ *
+ * Responsibilities:
+ * - Create scanner instances when project opens
+ * - Store scanner instances for reuse
+ * - Dispose scanners when project closes
+ *
+ * This is a project-level service. Each open project gets its own registry.
+ * Scanners are lazily initialized on first access.
+ *
+ * Mirrors the JetBrains ScannerRegistry pattern.
+ */
+public class ScannerRegistry {
+
+ private static final String LOG_TAG = "[SCANNER-REGISTRY]";
+
+ // Session property key for storing registry on project
+ public static final String REGISTRY_KEY = ScannerRegistry.class.getName() + ".INSTANCE";
+
+ private final IProject project;
+ private final ConcurrentHashMap scanners = new ConcurrentHashMap<>();
+ private volatile boolean disposed = false;
+ private final Object lock = new Object();
+
+ /**
+ * Create a registry for a project.
+ *
+ * @param project Eclipse project
+ */
+ public ScannerRegistry(IProject project) {
+ this.project = project;
+ CxLogger.info(LOG_TAG + " Created for project: " + project.getName());
+ }
+
+ /**
+ * Deregister and dispose all scanners (on project close).
+ * Synchronized to prevent race with getScannerService() lazy creation.
+ */
+ public void deregisterAllScanners() {
+ synchronized (lock) {
+ CxLogger.info(LOG_TAG + " Deregistering all scanners for: " + project.getName());
+
+ // Dispose each scanner
+ scanners.forEach((type, scanner) -> {
+ try {
+ if (scanner instanceof AutoCloseable) {
+ ((AutoCloseable) scanner).close();
+ }
+ CxLogger.info(LOG_TAG + "Disposed scanner: " + type);
+ } catch (Exception e) {
+ CxLogger.warning(LOG_TAG + " Error disposing scanner " + type + ": " +
+ e.getMessage());
+ }
+ });
+
+ scanners.clear();
+ disposed = true;
+ CxLogger.info(LOG_TAG + " All scanners disposed");
+ }
+ }
+
+ /**
+ * Get a scanner service by type.
+ * Lazily creates the scanner on first access.
+ *
+ * Synchronized with deregisterAllScanners() to prevent race:
+ * if project closes while scanner is being created, the new instance
+ * will be disposed immediately and not leak.
+ *
+ * @param type Scanner type (OSS, SECRETS, etc.)
+ * @return Scanner instance, or null if scanner type not supported
+ */
+ public Object getScannerService(ScannerType type) {
+ synchronized (lock) {
+ if (disposed) {
+ CxLogger.warning(LOG_TAG + " Registry is disposed");
+ return null;
+ }
+
+ return scanners.computeIfAbsent(type.name(), key -> {
+ CxLogger.info(LOG_TAG + " Creating scanner: " + type);
+ // Scanner creation will be implemented in Phase 2
+ return createScannerInstance(type);
+ });
+ }
+ }
+
+ /**
+ * Create a scanner instance by type.
+ * Creates implementations of ScannerService that delegate to the new scanner
+ * commands.
+ *
+ * @param type Scanner type
+ * @return Scanner instance
+ */
+ private Object createScannerInstance(ScannerType type) {
+ try {
+ CxLogger.info(LOG_TAG + " Creating scanner instance for: " + type.getDisplayName());
+ Object scanner = null;
+
+ switch (type) {
+ case OSS:
+ scanner = new OssScannerServiceImpl(project);
+ break;
+ case SECRETS:
+ scanner = new SecretsScannerServiceImpl(project);
+ break;
+ case CONTAINERS:
+ scanner = new ContainerScannerServiceImpl(project);
+ break;
+ case IAC:
+ scanner = new IacScannerServiceImpl(project);
+ break;
+ case ASCA:
+ scanner = new AscaScannerServiceImpl(project);
+ break;
+ default:
+ return null;
+ }
+
+ if (scanner != null) {
+ CxLogger.info(LOG_TAG + "Successfully created scanner: " + type.getDisplayName());
+ } else {
+ CxLogger.warning(LOG_TAG + "Scanner returned null: " + type.getDisplayName());
+ }
+ return scanner;
+ } catch (Exception e) {
+ CxLogger.error(LOG_TAG + "Error creating scanner " + type.getDisplayName() + ": " + e.getMessage(), e);
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Inner class implementations of ScannerService that bridge to new scanner
+ * commands.
+ * These are minimal adapters that delegate to the proper scanner packages.
+ */
+
+ private static class OssScannerServiceImpl implements ScannerService