diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69a14b28..53bc804f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,38 @@ jobs: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-maven- + - name: Configure echo mirror for dependency resolution + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << EOF + + + + + echo-repo + * + https://maven.echohq.com + + + + + echo-repo + + ${ECHO_LIBRARIES_ACCESS_KEY} + + + + EOF + env: + ECHO_LIBRARIES_ACCESS_KEY: ${{ secrets.ECHO_LIBRARIES_ACCESS_KEY }} + - &install-pinned-maven + name: Install pinned Maven + run: | + MAVEN_VERSION=3.9.16 # Resolved through the echo mirror configured in ~/.m2/settings.xml + # Run outside the repo: the root pom loads the Tycho extension, which fails on the runner's Maven + (cd "${RUNNER_TEMP}" && mvn -B -q dependency:copy -Dartifact=org.apache.maven:apache-maven:${MAVEN_VERSION}:tar.gz:bin -DoutputDirectory=/tmp/maven-dist) + sudo tar -xzf /tmp/maven-dist/apache-maven-${MAVEN_VERSION}-bin.tar.gz -C /opt + echo "/opt/apache-maven-${MAVEN_VERSION}/bin" >> "$GITHUB_PATH" - name: Run UI Tests env: CX_BASE_URI: ${{ secrets.CX_BASE_URI }} @@ -71,6 +103,31 @@ jobs: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-maven- + - name: Configure echo mirror for dependency resolution + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << EOF + + + + + echo-repo + * + https://maven.echohq.com + + + + + echo-repo + + ${ECHO_LIBRARIES_ACCESS_KEY} + + + + EOF + env: + ECHO_LIBRARIES_ACCESS_KEY: ${{ secrets.ECHO_LIBRARIES_ACCESS_KEY }} + - *install-pinned-maven - name: Run Integration Tests env: CX_BASE_URI: ${{ secrets.CX_BASE_URI }} @@ -110,6 +167,31 @@ jobs: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-maven- + - name: Configure echo mirror for dependency resolution + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << EOF + + + + + echo-repo + * + https://maven.echohq.com + + + + + echo-repo + + ${ECHO_LIBRARIES_ACCESS_KEY} + + + + EOF + env: + ECHO_LIBRARIES_ACCESS_KEY: ${{ secrets.ECHO_LIBRARIES_ACCESS_KEY }} + - *install-pinned-maven - name: Run Unit Tests with Coverage env: CX_BASE_URI: ${{ secrets.CX_BASE_URI }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 958a0e3a..bcb6a440 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,6 +108,39 @@ jobs: restore-keys: | ${{ runner.os }}-maven- + - name: Configure echo mirror for dependency resolution + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << EOF + + + + + echo-repo + * + https://maven.echohq.com + + + + + echo-repo + + ${ECHO_LIBRARIES_ACCESS_KEY} + + + + EOF + env: + ECHO_LIBRARIES_ACCESS_KEY: ${{ secrets.ECHO_LIBRARIES_ACCESS_KEY }} + + - name: Install pinned Maven + run: | + MAVEN_VERSION=3.9.16 # Resolved through the echo mirror configured in ~/.m2/settings.xml + # Run outside the repo: the root pom loads the Tycho extension, which fails on the runner's Maven + (cd "${RUNNER_TEMP}" && mvn -B -q dependency:copy -Dartifact=org.apache.maven:apache-maven:${MAVEN_VERSION}:tar.gz:bin -DoutputDirectory=/tmp/maven-dist) + sudo tar -xzf /tmp/maven-dist/apache-maven-${MAVEN_VERSION}-bin.tar.gz -C /opt + echo "/opt/apache-maven-${MAVEN_VERSION}/bin" >> "$GITHUB_PATH" + - name: Update the POM version env: VERSION: ${{ inputs.tag }} diff --git a/Cloud.md b/Claude.md similarity index 95% rename from Cloud.md rename to Claude.md index 459c1fac..93e928be 100644 --- a/Cloud.md +++ b/Claude.md @@ -137,6 +137,14 @@ Import into Eclipse: - `File → Import → Maven → Existing Maven Projects` - Select the repo root — all four modules will be detected +### Build Configuration + +All Maven dependencies and plugins are resolved through the **Echo HQ Maven Repository** (`https://maven.echohq.com`), which serves CVE-patched builds of open-source libraries. + +- In CI/CD, each workflow writes `~/.m2/settings.xml` with an `echo-repo` mirror (`mirrorOf *`) and server credentials taken from the `ECHO_LIBRARIES_ACCESS_KEY` secret, the same way as `ast-cli-java-wrapper`. +- For local builds, add the same `echo-repo` mirror and server to your `~/.m2/settings.xml`, with an empty username and your access key as the password. Never commit the key. +- Eclipse platform bundles still come from the p2 repositories declared in `pom.xml`, and the runtime JARs bundled in the `lib/` folders are committed to the repo. Echo does not affect either of them. + ### Build from CLI ```bash diff --git a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/enums/SeverityExtendedTest.java b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/enums/SeverityExtendedTest.java index 3e1f0d67..1823826f 100644 --- a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/enums/SeverityExtendedTest.java +++ b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/enums/SeverityExtendedTest.java @@ -79,7 +79,7 @@ void testEnumOrdinal_correctSequence() { @Test void testValues_returnsAllConstants() { Severity[] values = Severity.values(); - assertEquals(8, values.length); + assertEquals(6, values.length); } @Test @@ -88,7 +88,6 @@ void testValues_containsAllExpected() { boolean hasCritical = false; boolean hasHigh = false; boolean hasLow = false; - boolean hasGroupBy = false; for (Severity s : values) { if (s == Severity.CRITICAL) hasCritical = true; @@ -99,7 +98,6 @@ void testValues_containsAllExpected() { assertTrue(hasCritical); assertTrue(hasHigh); assertTrue(hasLow); - assertTrue(hasGroupBy); } @Test diff --git a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/enums/SeverityTest.java b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/enums/SeverityTest.java index 52126ba1..6fa8c3c7 100644 --- a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/enums/SeverityTest.java +++ b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/enums/SeverityTest.java @@ -47,6 +47,6 @@ void testGetSeverity_roundTrip_allValues() { @Test void testEnumValues_count() { - assertEquals(8, Severity.values().length); + assertEquals(6, Severity.values().length); } } diff --git a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/properties/PreferencesTest.java b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/properties/PreferencesTest.java index 4c8c6d91..22f4968e 100644 --- a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/properties/PreferencesTest.java +++ b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/properties/PreferencesTest.java @@ -5,7 +5,6 @@ import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.IPreferencesService; -import org.eclipse.jface.preference.IPreferenceStore; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,28 +12,24 @@ import org.mockito.MockedStatic; import org.mockito.MockitoAnnotations; -import com.checkmarx.eclipse.Activator; import com.checkmarx.eclipse.common.preferences.Preferences; class PreferencesTest { - @Mock - private IPreferencesService mockPreferencesService; - - @Mock - private IPreferenceStore mockPreferenceStore; + private static final String[] STORED_TEST_KEYS = { "testKey", "key1", "key2" }; @Mock - private Activator mockActivator; + private IPreferencesService mockPreferencesService; private MockedStatic platformMock; - private MockedStatic activatorMock; + private IPreferencesService realPreferencesService; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); + // Captured before Platform is mocked: reading values back through Preferences.STORE needs the real service + realPreferencesService = Platform.getPreferencesService(); platformMock = mockStatic(Platform.class); - activatorMock = mockStatic(Activator.class); } @AfterEach @@ -42,8 +37,8 @@ void tearDown() { if (platformMock != null) { platformMock.close(); } - if (activatorMock != null) { - activatorMock.close(); + for (String key : STORED_TEST_KEYS) { + Preferences.STORE.setToDefault(key); } } @@ -118,33 +113,19 @@ void testGetAdditionalOptions_returnNull_whenNotSet() { @Test void testStore_setsValueInPreferenceStore() { - activatorMock.when(Activator::getDefault).thenReturn(mockActivator); - when(mockActivator.getPreferenceStore()).thenReturn(mockPreferenceStore); - + useRealPreferencesService(); Preferences.store("testKey", "testValue"); - verify(mockActivator).getPreferenceStore(); - verify(mockPreferenceStore).setValue("testKey", "testValue"); + assertEquals("testValue", Preferences.STORE.getString("testKey")); } @Test void testStore_withEmptyValue() { - activatorMock.when(Activator::getDefault).thenReturn(mockActivator); - when(mockActivator.getPreferenceStore()).thenReturn(mockPreferenceStore); - + useRealPreferencesService(); + Preferences.store("testKey", "value"); Preferences.store("testKey", ""); - verify(mockPreferenceStore).setValue("testKey", ""); - } - - @Test - void testStore_withNullValue() { - activatorMock.when(Activator::getDefault).thenReturn(mockActivator); - when(mockActivator.getPreferenceStore()).thenReturn(mockPreferenceStore); - - Preferences.store("testKey", null); - - verify(mockPreferenceStore).setValue("testKey", null); + assertEquals("", Preferences.STORE.getString("testKey")); } @Test @@ -179,13 +160,15 @@ void testGetPref_multipleCallsWithDifferentKeys() { @Test void testStore_multipleValuesSequentially() { - activatorMock.when(Activator::getDefault).thenReturn(mockActivator); - when(mockActivator.getPreferenceStore()).thenReturn(mockPreferenceStore); - + useRealPreferencesService(); Preferences.store("key1", "value1"); Preferences.store("key2", "value2"); - verify(mockPreferenceStore).setValue("key1", "value1"); - verify(mockPreferenceStore).setValue("key2", "value2"); + assertEquals("value1", Preferences.STORE.getString("key1")); + assertEquals("value2", Preferences.STORE.getString("key2")); + } + + private void useRealPreferencesService() { + platformMock.when(Platform::getPreferencesService).thenReturn(realPreferencesService); } } diff --git a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/DataProviderTest.java b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/DataProviderTest.java index 4e3955e0..a6d85b03 100644 --- a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/DataProviderTest.java +++ b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/DataProviderTest.java @@ -43,6 +43,12 @@ void setUp() { FilterState.resetFilters(); } + /** These tests call the real CLI, so they can only run when CX_APIKEY is provided (e.g. not on fork PRs). */ + private static void assumeCredentialsConfigured() { + Assumptions.assumeTrue(Environment.API_KEY != null && !Environment.API_KEY.isBlank(), + "CX_APIKEY is not set; skipping test that needs a live Checkmarx server"); + } + @Test void testSingletonInstance() { DataProvider instance1 = DataProvider.getInstance(); @@ -66,12 +72,14 @@ void testSetAndGetCurrentResults() { @Test void testGetProjectsReturnsList() throws Exception { + assumeCredentialsConfigured(); List projects = dataProvider.getProjects(); assertNotNull(projects); } @Test void testGetProjectsByNameReturnsList() throws Exception { + assumeCredentialsConfigured(); List projects = dataProvider.getProjects(TEST_PROJECT); assertNotNull(projects); } diff --git a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/actions/ToolBarActionsTest.java b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/actions/ToolBarActionsTest.java index af8d8612..42b1a83d 100644 --- a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/actions/ToolBarActionsTest.java +++ b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/actions/ToolBarActionsTest.java @@ -23,7 +23,9 @@ import org.mockito.Mockito; import com.checkmarx.eclipse.enums.ActionName; +import com.checkmarx.eclipse.enums.GroupingMode; import com.checkmarx.eclipse.views.DataProvider; +import com.checkmarx.eclipse.views.GlobalSettings; import com.checkmarx.eclipse.enums.PluginListenerType; import com.checkmarx.eclipse.common.enums.Severity; @@ -137,6 +139,9 @@ void testRefreshToolbarRecreatesActions() { void testGroupBySeverityAction() { // createGroupByActions() runs inside a background Job so we test the toggle directly. FilterState.groupBySeverity = false; + try (MockedStatic gs = Mockito.mockStatic(GlobalSettings.class)) { + FilterState.setGroupingMode(GroupingMode.SEVERITY); + } assertTrue(FilterState.groupBySeverity); } diff --git a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/filters/FilterStateExtendedTest.java b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/filters/FilterStateExtendedTest.java index 089d188b..4fd26a37 100644 --- a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/filters/FilterStateExtendedTest.java +++ b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/filters/FilterStateExtendedTest.java @@ -9,6 +9,7 @@ import org.mockito.MockitoAnnotations; import com.checkmarx.eclipse.common.enums.Severity; +import com.checkmarx.eclipse.enums.GroupingMode; import com.checkmarx.eclipse.enums.State; import com.checkmarx.eclipse.views.GlobalSettings; import com.checkmarx.eclipse.views.filters.FilterState; @@ -87,6 +88,7 @@ void testSetState_groupBySeverityToggle() { boolean beforeState = FilterState.groupBySeverity; try (MockedStatic mockSettings = mockStatic(GlobalSettings.class)) { + FilterState.setGroupingMode(GroupingMode.SEVERITY); assertNotEquals(beforeState, FilterState.groupBySeverity); } } diff --git a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/filters/FilterStateTest.java b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/filters/FilterStateTest.java index ad5da5c0..8da6c6a6 100644 --- a/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/filters/FilterStateTest.java +++ b/checkmarx-ast-eclipse-plugin-tests/src/test/java/checkmarx/ast/eclipse/plugin/tests/unit/views/filters/FilterStateTest.java @@ -13,6 +13,7 @@ import org.mockito.Mockito; import com.checkmarx.eclipse.common.enums.Severity; +import com.checkmarx.eclipse.enums.GroupingMode; import com.checkmarx.eclipse.enums.State; import com.checkmarx.eclipse.views.GlobalSettings; import com.checkmarx.eclipse.views.filters.FilterState; @@ -136,6 +137,7 @@ void testSetState_info_togglesFromFalse() { void testSetState_groupBySeverity_toggles() { FilterState.groupBySeverity = true; try (MockedStatic gs = Mockito.mockStatic(GlobalSettings.class)) { + FilterState.setGroupingMode(GroupingMode.SEVERITY); } assertFalse(FilterState.groupBySeverity); } @@ -144,6 +146,7 @@ void testSetState_groupBySeverity_toggles() { void testSetState_groupByQueryName_toggles() { FilterState.groupByQueryName = false; try (MockedStatic gs = Mockito.mockStatic(GlobalSettings.class)) { + FilterState.setGroupingMode(GroupingMode.QUERY_NAME); } assertTrue(FilterState.groupByQueryName); } @@ -152,6 +155,7 @@ void testSetState_groupByQueryName_toggles() { void testSetState_groupByStateName_toggles() { FilterState.groupByStateName = false; try (MockedStatic gs = Mockito.mockStatic(GlobalSettings.class)) { + FilterState.setGroupingMode(GroupingMode.STATE_NAME); } assertTrue(FilterState.groupByStateName); } diff --git a/checkmarx-ast-eclipse-plugin/.classpath b/checkmarx-ast-eclipse-plugin/.classpath index 56a85caa..7ec84876 100644 --- a/checkmarx-ast-eclipse-plugin/.classpath +++ b/checkmarx-ast-eclipse-plugin/.classpath @@ -15,8 +15,8 @@ - - + + diff --git a/common-lib/.classpath b/common-lib/.classpath index 082d5a03..aa6f37ef 100644 --- a/common-lib/.classpath +++ b/common-lib/.classpath @@ -9,11 +9,11 @@ - - + + diff --git a/common-lib/META-INF/MANIFEST.MF b/common-lib/META-INF/MANIFEST.MF index dd946b85..8c4e98a2 100644 --- a/common-lib/META-INF/MANIFEST.MF +++ b/common-lib/META-INF/MANIFEST.MF @@ -6,8 +6,8 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: Checkmarx Bundle-ClassPath: ., lib/ast-cli-java-wrapper-2.4.31.jar, - lib/jackson-core-2.21.4.jar, - lib/jackson-databind-2.21.5.jar, + lib/jackson-core-2.22.3.jar, + lib/jackson-databind-2.22.3.jar, lib/jackson-annotations-2.21.jar, lib/slf4j-api-2.0.17.jar, lib/slf4j-simple-2.0.17.jar, diff --git a/common-lib/build.properties b/common-lib/build.properties index d53d55b3..b29facd3 100644 --- a/common-lib/build.properties +++ b/common-lib/build.properties @@ -1,8 +1,8 @@ output.. = bin/ bin.includes = META-INF/,\ lib/ast-cli-java-wrapper-2.4.31.jar,\ - lib/jackson-core-2.21.4.jar,\ - lib/jackson-databind-2.21.5.jar,\ + lib/jackson-core-2.22.3.jar,\ + lib/jackson-databind-2.22.3.jar,\ lib/jackson-annotations-2.21.jar,\ lib/slf4j-api-2.0.17.jar,\ lib/slf4j-simple-2.0.17.jar,\ diff --git a/common-lib/lib/jackson-core-2.21.4.jar b/common-lib/lib/jackson-core-2.21.4.jar deleted file mode 100644 index d43b85b8..00000000 --- a/common-lib/lib/jackson-core-2.21.4.jar +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b40a06396f239f8de2da57419adde6e94e5edc18a2171d471ea05eeed4e5c2d -size 594183 diff --git a/common-lib/lib/jackson-core-2.22.3.jar b/common-lib/lib/jackson-core-2.22.3.jar new file mode 100644 index 00000000..a16678a6 --- /dev/null +++ b/common-lib/lib/jackson-core-2.22.3.jar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a501126a385b25841915d839508f8a66e2a0dbc8a6709d055ef3b3e852b094c +size 594776 diff --git a/common-lib/lib/jackson-databind-2.21.5.jar b/common-lib/lib/jackson-databind-2.21.5.jar deleted file mode 100644 index 188a54f7..00000000 --- a/common-lib/lib/jackson-databind-2.21.5.jar +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:507418c0fafd38b2b2cfb704521630da613a8e4cc8381195a6f418017883e2c0 -size 1705906 diff --git a/common-lib/lib/jackson-databind-2.22.3.jar b/common-lib/lib/jackson-databind-2.22.3.jar new file mode 100644 index 00000000..6cad31e3 --- /dev/null +++ b/common-lib/lib/jackson-databind-2.22.3.jar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:556db5439e206114346043f68d200497dc96a0bca62a360a81784092ebd0e0a9 +size 1709604 diff --git a/devassist-lib/.classpath b/devassist-lib/.classpath index d011c85e..98c96848 100644 --- a/devassist-lib/.classpath +++ b/devassist-lib/.classpath @@ -5,8 +5,8 @@ - - + + diff --git a/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/McpSettingsInjector.java b/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/McpSettingsInjector.java index 7b2dc6cc..e4698c09 100644 --- a/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/McpSettingsInjector.java +++ b/devassist-lib/src/com/checkmarx/eclipse/devassist/configuration/McpSettingsInjector.java @@ -6,6 +6,9 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; @@ -59,6 +62,8 @@ public final class McpSettingsInjector { /** Bundle symbolic name of GitHub Copilot for Eclipse's UI plugin. */ private static final String COPILOT_UI_BUNDLE_ID = "com.microsoft.copilot.eclipse.ui"; + private static final Set DEV_ENV_IAM_HOSTS = Set.of("iam-dev.dev.cxast.net"); + private static final Pattern IAM_HOST_LABEL = Pattern.compile("(^|\\.)iam\\."); /** Preference key Copilot reads its MCP server JSON from (Constants.MCP). */ private static final String MCP_PREFERENCE_KEY = "mcp"; @@ -304,23 +309,29 @@ private static String deriveBaseUrlFromIssuer(String issuer) { CxLogger.info(LOG_TAG + " Issuer is null/empty, using fallback base URL"); return FALLBACK_BASE; } - + try { CxLogger.info(LOG_TAG + " Deriving base URL from issuer: " + issuer); String host = URI.create(issuer).getHost(); - - if (host != null && host.contains("iam.checkmarx")) { - String newHost = host.replace("iam", "ast"); - String baseUrl = "https://" + newHost; - CxLogger.info(LOG_TAG + "Derived base URL: " + baseUrl); - return baseUrl; + + if (host == null || host.isBlank()) { + CxLogger.info(LOG_TAG + " Issuer has no host, using fallback"); + return FALLBACK_BASE; + } + + + if (DEV_ENV_IAM_HOSTS.contains(host)) { + CxLogger.info(LOG_TAG + " Issuer host '" + host + "' is a dev env, using fallback base URL"); + return FALLBACK_BASE; } - - CxLogger.info(LOG_TAG + " Host does not match iam.checkmarx pattern, using fallback"); - return FALLBACK_BASE; + + // Multi-tenant: swap only the "iam" host label (e.g. eu.iam.checkmarx.net -> eu.ast.checkmarx.net). + // Single-tenant: the issuer host is the tenant base URL. + Matcher iamLabel = IAM_HOST_LABEL.matcher(host); + return "https://" + (iamLabel.find() ? iamLabel.replaceFirst("$1ast.") : host); } catch (Exception e) { CxLogger.warning(LOG_TAG + " Failed to derive base URL from issuer: " + e.getMessage()); return FALLBACK_BASE; } } -} + }