VYPR
Moderate severityNVD Advisory· Published Jul 12, 2023· Updated Nov 7, 2024

CVE-2023-37953

CVE-2023-37953

Description

A missing permission check in Jenkins mabl Plugin 0.0.46 and earlier allows attackers with Overall/Read permission to connect to an attacker-specified URL using attacker-specified credentials IDs obtained through another method, capturing credentials stored in Jenkins.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Missing permission check in Jenkins mabl Plugin allows attackers with Overall/Read to capture credentials by connecting to attacker-controlled URL.

Vulnerability

Description

The mabl Plugin for Jenkins, versions 0.0.46 and earlier, contains a missing permission check that allows attackers with Overall/Read permission to connect to an attacker-specified URL using attacker-specified credential IDs obtained through another method [1][2][3]. This flaw exists in the plugin's implementation, which does not verify that the user has the necessary permissions to perform such connections.

Exploitation

An attacker must have Overall/Read permission on the Jenkins instance and must obtain credential IDs (e.g., via another vulnerability or information disclosure). The attacker can then configure the mabl Plugin to connect to a URL they control, using those credential IDs [1][4]. No additional authentication or privileges are required beyond Overall/Read.

Impact

Successful exploitation allows the attacker to capture credentials stored in Jenkins by tricking the plugin into sending them to an attacker-controlled server [1][2][3]. This could lead to further compromise of the Jenkins environment or other integrated systems.

Mitigation

The vulnerability is fixed in mabl Plugin version 0.0.47 [1][2][4]. Users should upgrade immediately. The fix also includes an increased minimum Jenkins version [4]. No workarounds are available.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
com.mabl.integration.jenkins:mabl-integrationMaven
< 0.0.470.0.47

Affected products

3

Patches

1
4e21f498b070

[SECURITY-3127][SECURITY-3137] (#64)

8 files changed · +281 148
  • pom.xml+1 1 modified
    @@ -21,7 +21,7 @@
     
         <properties>
             <!-- NOTE: this version drives the minimum Jenkins version that the plugin can be installed on -->
    -        <jenkins.version>2.222.4</jenkins.version>
    +        <jenkins.version>2.319.1</jenkins.version>
             <java.level>8</java.level>
             <no-test-jar>false</no-test-jar>
     
    
  • README.md+6 2 modified
    @@ -20,7 +20,7 @@ This plugin allows easy launching of [mabl](https://www.mabl.com) tests as a ste
       * [Deployment](#deployment)
     
     ## Plugin Installation
    -Install the [plugin](https://plugins.jenkins.io/mabl-integration) into your Jenkins `v2.222+` server from the *Available Plugins* tab by searching for "mabl".
    +Install the [plugin](https://plugins.jenkins.io/mabl-integration) into your Jenkins `v2.319.1+` server from the *Available Plugins* tab by searching for "mabl".
     
     ### Features
     
    @@ -30,7 +30,7 @@ Install the [plugin](https://plugins.jenkins.io/mabl-integration) into your Jenk
     
     ### Requirements
     
    --   Minimum Jenkins version: *2.222.4*
    +-   Minimum Jenkins version: *2.319.1*
     -   Minimum Java runtime version: *8*
     -   mabl API key
         -   See [integration
    @@ -186,6 +186,10 @@ Note that
     
     ### Change Log
     
    +#### v0.0.47 (07/06/2023)
    +- Patched security vulnerabilities
    +- Increased minimum Jenkins version
    +
     #### v0.0.46 (11/15/2022)
     - Replace usages of deprecated imports
     
    
  • src/main/java/com/mabl/integration/jenkins/MablStepBuilder.java+156 76 modified
    @@ -1,7 +1,13 @@
     package com.mabl.integration.jenkins;
     
    +import com.cloudbees.plugins.credentials.CredentialsMatchers;
     import com.cloudbees.plugins.credentials.CredentialsProvider;
     import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
    +import hudson.model.Job;
    +import hudson.model.Queue;
    +import hudson.model.queue.Tasks;
    +import hudson.security.ACL;
    +import jenkins.model.Jenkins;
     import org.jenkinsci.plugins.plaincredentials.StringCredentials;
     import com.mabl.integration.jenkins.domain.GetApiKeyResult;
     import com.mabl.integration.jenkins.domain.GetApplicationsResult;
    @@ -16,7 +22,6 @@
     import hudson.model.Result;
     import hudson.model.Run;
     import hudson.model.TaskListener;
    -import hudson.security.ACL;
     import hudson.tasks.BuildStepDescriptor;
     import hudson.tasks.Builder;
     import hudson.util.FormValidation;
    @@ -31,26 +36,25 @@
     import org.kohsuke.stapler.DataBoundSetter;
     import org.kohsuke.stapler.QueryParameter;
     import org.kohsuke.stapler.StaplerRequest;
    +import org.kohsuke.stapler.verb.POST;
     
     import javax.annotation.Nonnull;
     import java.io.File;
     import java.io.IOException;
     import java.io.PrintStream;
     import java.util.Collections;
    -import java.util.List;
     import java.util.concurrent.ExecutionException;
     import java.util.concurrent.ExecutorService;
     import java.util.concurrent.Executors;
     import java.util.concurrent.Future;
     import java.util.concurrent.TimeoutException;
     import java.util.logging.Logger;
     
    -import static com.cloudbees.plugins.credentials.CredentialsProvider.*;
     import static com.mabl.integration.jenkins.MablStepConstants.BUILD_STEP_DISPLAY_NAME;
     import static com.mabl.integration.jenkins.MablStepConstants.EXECUTION_STATUS_POLLING_INTERNAL_MILLISECONDS;
     import static com.mabl.integration.jenkins.MablStepConstants.EXECUTION_TIMEOUT_SECONDS;
    -import static com.mabl.integration.jenkins.MablStepConstants.DEFAULT_MABL_APP_BASE_URL;
    -import static com.mabl.integration.jenkins.MablStepConstants.DEFAULT_MABL_API_BASE_URL;
    +import static com.mabl.integration.jenkins.MablStepConstants.MABL_APP_BASE_URL;
    +import static com.mabl.integration.jenkins.MablStepConstants.MABL_API_BASE_URL;
     import static com.mabl.integration.jenkins.MablStepConstants.PLUGIN_SYMBOL;
     import static com.mabl.integration.jenkins.MablStepConstants.TEST_OUTPUT_XML_FILENAME;
     import static java.util.concurrent.TimeUnit.SECONDS;
    @@ -175,10 +179,22 @@ public void perform(
         ) throws InterruptedException {
     
             final PrintStream outputStream = listener.getLogger();
    +
    +        StringCredentials credentials = CredentialsProvider.findCredentialById(
    +            restApiKeyId,
    +            StringCredentials.class,
    +            run,
    +            Collections.emptyList()
    +        );
    +
    +        if (credentials == null) {
    +            throw new IllegalStateException("No credentials found for API key provided");
    +        }
    +
             final MablRestApiClient client = new MablRestApiClientImpl(
    -                StringUtils.isBlank(apiBaseUrl) ? DEFAULT_MABL_API_BASE_URL : apiBaseUrl,
    -                getRestApiSecret(getRestApiKeyId()),
    -                StringUtils.isBlank(appBaseUrl) ? DEFAULT_MABL_APP_BASE_URL : appBaseUrl,
    +                MABL_API_BASE_URL,
    +                credentials.getSecret(),
    +                MABL_APP_BASE_URL,
                     disableSslVerification
             );
     
    @@ -259,23 +275,6 @@ private EnvVars getEnvironmentVars(Run<?, ?> build, TaskListener listener) {
             return environmentVars;
         }
     
    -    static Secret getRestApiSecret(final String restApiKeyId) {
    -        if (restApiKeyId == null) {
    -            return null;
    -        }
    -
    -        Secret secretKey = null;
    -        List<StringCredentials> stringCredentials =
    -                CredentialsProvider.lookupCredentials(StringCredentials.class, (Item) null, ACL.SYSTEM, Collections.emptyList());
    -        for (StringCredentials cred : stringCredentials) {
    -            if (restApiKeyId.equals(cred.getId())) {
    -                secretKey = cred.getSecret();
    -                break;
    -            }
    -        }
    -        return secretKey;
    -    }
    -
         /**
          * Descriptor used in views. Centralized metadata store for all {@link MablStepBuilder} instances.
          */
    @@ -312,43 +311,67 @@ public String getDisplayName() {
                 return BUILD_STEP_DISPLAY_NAME;
             }
     
    -
    +        @POST
             public FormValidation doValidateForm(
                     @QueryParameter("restApiKeyId") final String restApiKeyId,
                     @QueryParameter("environmentId") final String environmentId,
                     @QueryParameter("applicationId") final String applicationId,
                     @QueryParameter("disableSslVerification") final boolean disableSslVerification,
    -                @QueryParameter("apiBaseUrl") final String apiBaseUrl,
    -                @QueryParameter("appBaseUrl") final String appBaseUrl
    +                @AncestorInPath final Job job
             ) {
    -            return MablStepBuilderValidator.validateForm(
    -                    restApiKeyId, environmentId, applicationId, disableSslVerification, apiBaseUrl, appBaseUrl);
    +            if (job == null) {
    +                Jenkins.get().checkPermission(Jenkins.ADMINISTER);
    +            } else {
    +                job.checkPermission(Item.CONFIGURE);
    +            }
    +            return MablStepBuilderValidator.validateForm(restApiKeyId, environmentId, applicationId, disableSslVerification, job);
             }
     
             public ListBoxModel doFillRestApiKeyIdItems(
                     @AncestorInPath Item item,
    -                @QueryParameter String restApiKeyId) {
    +                @QueryParameter String restApiKeyId
    +        ) {
    +            Jenkins.get().checkPermission(Job.CONFIGURE);
                 StandardListBoxModel result = new StandardListBoxModel();
    -            if (restApiKeyId != null) {
    -                result.add(restApiKeyId);
    -            }
    -            List<StringCredentials> creds =
    -                    lookupCredentials(StringCredentials.class, item, ACL.SYSTEM,
    -                            Collections.emptyList());
    -            for (StringCredentials cred : creds) {
    -                result.add(cred.getId());
    +            if (item == null) {
    +                if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
    +                    return result.includeCurrentValue(restApiKeyId);
    +                }
    +            } else {
    +                if (!item.hasPermission(Item.EXTENDED_READ)
    +                    && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                    return result.includeCurrentValue(restApiKeyId);
    +                }
                 }
    -            return result.includeEmptyValue();
    +
    +            return result
    +                .includeEmptyValue()
    +                .includeMatchingAs(
    +                    item instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) item) : ACL.SYSTEM,
    +                    item,
    +                    StringCredentials.class,
    +                    Collections.emptyList(),
    +                    CredentialsMatchers.always()
    +                );
             }
     
             public FormValidation doCheckRestApiKeyIds(
                     @AncestorInPath Item item,
                     @QueryParameter String value
             ) {
    +            if (item == null) {
    +                if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
    +                    return FormValidation.ok();
    +                }
    +            } else {
    +                if (!item.hasPermission(Item.EXTENDED_READ)
    +                    && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                    return FormValidation.warning("Insufficient permissions");
    +                }
    +            }
    +
                 if (StringUtils.isBlank(value)) {
                     return FormValidation.warning("Provide a credentials ID");
    -            } else if (!item.hasPermission(Item.EXTENDED_READ) && !item.hasPermission(USE_ITEM)) {
    -                return FormValidation.warning("Insufficient permissions");
                 }
     
                 if (value.startsWith("${") && value.endsWith("}")) {
    @@ -358,27 +381,48 @@ public FormValidation doCheckRestApiKeyIds(
                 return FormValidation.ok();
             }
     
    +        @POST
             public ListBoxModel doFillApplicationIdItems(
                     @QueryParameter String restApiKeyId,
                     @QueryParameter boolean disableSslVerification,
    -                @QueryParameter String apiBaseUrl,
    -                @QueryParameter String appBaseUrl
    +                @AncestorInPath Item item
             ) {
    +            Jenkins.get().checkPermission(Job.CONFIGURE);
                 if (StringUtils.isBlank(restApiKeyId)) {
                     return getSelectValidApiKeyListBoxModel();
                 }
    -            Secret secretKey = getRestApiSecret(restApiKeyId);
    -            if (secretKey != null) {
    -                final MablRestApiClient client =
    -                        createMablRestApiClient(
    -                                secretKey,
    -                                disableSslVerification,
    -                                apiBaseUrl,
    -                                appBaseUrl
    -                );
    -                return getApplicationIdItems(client);
    +
    +            if (item == null) {
    +                if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
    +                    return new ListBoxModel();
    +                }
    +            } else {
    +                if (!item.hasPermission(Item.EXTENDED_READ)
    +                    && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                    return new ListBoxModel();
    +                }
                 }
    -            return new ListBoxModel();
    +
    +            StringCredentials credentials = CredentialsMatchers.firstOrNull(
    +                CredentialsProvider.lookupCredentials(
    +                    StringCredentials.class,
    +                    item,
    +                    item instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) item) : ACL.SYSTEM,
    +                    Collections.emptyList()
    +                ),
    +                CredentialsMatchers.withId(restApiKeyId)
    +            );
    +
    +            if (credentials == null) {
    +                throw new IllegalStateException("No credentials found for API key provided");
    +            }
    +
    +            final MablRestApiClient client =
    +                createMablRestApiClient(
    +                    credentials.getSecret(),
    +                    disableSslVerification
    +                );
    +            return getApplicationIdItems(client);
             }
     
             private ListBoxModel getApplicationIdItems(
    @@ -393,7 +437,7 @@ private ListBoxModel getApplicationIdItems(
                     String organizationId = apiKeyResult.organization_id;
                     GetApplicationsResult applicationsResult = client.getApplicationsResult(organizationId);
     
    -                items.add("", "");
    +                items.add("- none -", "");
                     for (GetApplicationsResult.Application application : applicationsResult.applications) {
                         items.add(application.name, application.id);
                     }
    @@ -406,23 +450,48 @@ private ListBoxModel getApplicationIdItems(
                 return getSelectValidApiKeyListBoxModel();
             }
     
    +        @POST
             public ListBoxModel doFillEnvironmentIdItems(
                     @QueryParameter String restApiKeyId,
                     @QueryParameter boolean disableSslVerification,
    -                @QueryParameter String apiBaseUrl,
    -                @QueryParameter String appBaseUrl
    +                @AncestorInPath Item item
             ) {
    +            Jenkins.get().checkPermission(Job.CONFIGURE);
                 if (StringUtils.isBlank(restApiKeyId)) {
                     return getSelectValidApiKeyListBoxModel();
                 }
     
    -            final Secret secretKey = getRestApiSecret(restApiKeyId);
    -            if (secretKey != null) {
    -                final MablRestApiClient client = createMablRestApiClient(
    -                        secretKey, disableSslVerification, apiBaseUrl, appBaseUrl);
    -                return getEnvironmentIdItems(client);
    +            if (item == null) {
    +                if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
    +                    return new ListBoxModel();
    +                }
    +            } else {
    +                if (!item.hasPermission(Item.EXTENDED_READ)
    +                    && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                    return new ListBoxModel();
    +                }
    +            }
    +
    +            StringCredentials credentials = CredentialsMatchers.firstOrNull(
    +                CredentialsProvider.lookupCredentials(
    +                    StringCredentials.class,
    +                    item,
    +                    item instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) item) : ACL.SYSTEM,
    +                    Collections.emptyList()
    +                ),
    +                CredentialsMatchers.withId(restApiKeyId)
    +            );
    +
    +            if (credentials == null) {
    +                throw new IllegalStateException("No credentials found for API key provided");
                 }
    -            return new ListBoxModel();
    +
    +            final MablRestApiClient client =
    +                createMablRestApiClient(
    +                    credentials.getSecret(),
    +                    disableSslVerification
    +                );
    +            return getEnvironmentIdItems(client);
             }
     
             private ListBoxModel getEnvironmentIdItems(final MablRestApiClient client) {
    @@ -436,7 +505,7 @@ private ListBoxModel getEnvironmentIdItems(final MablRestApiClient client) {
                     String organizationId = apiKeyResult.organization_id;
                     GetEnvironmentsResult environmentsResult = client.getEnvironmentsResult(organizationId);
     
    -                items.add("", "");
    +                items.add("- none -", "");
                     for (GetEnvironmentsResult.Environment environment : environmentsResult.environments) {
                         items.add(environment.name, environment.id);
                     }
    @@ -472,29 +541,40 @@ private static ListBoxModel getSelectValidApiKeyListBoxModel() {
     
             private static MablRestApiClient createMablRestApiClient(
                     final Secret key,
    -                final boolean disableSslVerification,
    -                final String apiBaseUrl,
    -                final String appBaseUrl
    +                final boolean disableSslVerification
             ) {
                 return new MablRestApiClientImpl(
    -                    StringUtils.isEmpty(apiBaseUrl) ? DEFAULT_MABL_API_BASE_URL : apiBaseUrl,
    +                    MABL_API_BASE_URL,
                         key,
    -                    StringUtils.isEmpty(appBaseUrl) ? DEFAULT_MABL_APP_BASE_URL : appBaseUrl,
    +                    MABL_APP_BASE_URL,
                         disableSslVerification);
             }
         }
     
         public @Nonnull static MablRestApiClient createMablRestApiClient(
                 final String restApiKeyId,
                 final boolean disableSslVerification,
    -            final String apiBaseUrl,
    -            final String appBaseUrl
    +            final Job job
         ) {
    +        StringCredentials credentials = CredentialsMatchers.firstOrNull(
    +            CredentialsProvider.lookupCredentials(
    +                StringCredentials.class,
    +                job,
    +                job instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) job) : ACL.SYSTEM,
    +                Collections.emptyList()
    +            ),
    +            CredentialsMatchers.withId(restApiKeyId)
    +        );
    +
    +        if (credentials == null) {
    +            throw new IllegalStateException("No credentials found for API key provided");
    +        }
    +
             return new MablRestApiClientImpl(
    -                StringUtils.isEmpty(apiBaseUrl) ? DEFAULT_MABL_API_BASE_URL : apiBaseUrl,
    -                getRestApiSecret(restApiKeyId),
    -                StringUtils.isEmpty(appBaseUrl) ? DEFAULT_MABL_APP_BASE_URL : appBaseUrl,
    +                MABL_API_BASE_URL,
    +                credentials.getSecret(),
    +                MABL_APP_BASE_URL,
                     disableSslVerification);
         }
     
    -}
    \ No newline at end of file
    +}
    
  • src/main/java/com/mabl/integration/jenkins/MablStepConstants.java+3 3 modified
    @@ -30,8 +30,8 @@ public class MablStepConstants {
     
         // Label for build steps drop down list
         static final String BUILD_STEP_DISPLAY_NAME = "Run mabl tests";
    -    public static final String DEFAULT_MABL_API_BASE_URL = "https://api.mabl.com";
    -    public static final String DEFAULT_MABL_APP_BASE_URL = "https://app.mabl.com";
    +    public static final String MABL_API_BASE_URL = "https://api.mabl.com";
    +    public static final String MABL_APP_BASE_URL = "https://app.mabl.com";
         static final int EXECUTION_TIMEOUT_SECONDS = 6 * 3600;
         static final int REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1000;
         static final long EXECUTION_STATUS_POLLING_INTERNAL_MILLISECONDS = 10000;
    @@ -61,4 +61,4 @@ static String getPluginVersion() {
             } catch (IOException ignored) {}
             return PLUGIN_VERSION_UNKNOWN;
         }
    -}
    \ No newline at end of file
    +}
    
  • src/main/java/com/mabl/integration/jenkins/validation/MablStepBuilderValidator.java+7 12 modified
    @@ -1,13 +1,12 @@
     package com.mabl.integration.jenkins.validation;
     
     import com.mabl.integration.jenkins.MablRestApiClient;
    +import hudson.model.Job;
     import hudson.util.FormValidation;
     
     import java.io.IOException;
     
     import static com.mabl.integration.jenkins.MablStepBuilder.createMablRestApiClient;
    -import static com.mabl.integration.jenkins.MablStepConstants.DEFAULT_MABL_API_BASE_URL;
    -import static com.mabl.integration.jenkins.MablStepConstants.DEFAULT_MABL_APP_BASE_URL;
     import static com.mabl.integration.jenkins.MablStepConstants.FORM_API_KEY_LABEL;
     import static com.mabl.integration.jenkins.MablStepConstants.FORM_APPLICATION_ID_LABEL;
     import static com.mabl.integration.jenkins.MablStepConstants.FORM_ENVIRONMENT_ID_LABEL;
    @@ -32,10 +31,10 @@ public class MablStepBuilderValidator {
         public static FormValidation validateForm(
                 String restApiKeyName,
                 String environmentId,
    -            String applicationId
    +            String applicationId,
    +            Job job
         ) {
    -        return validateForm(restApiKeyName, environmentId, applicationId, false,
    -                DEFAULT_MABL_API_BASE_URL, DEFAULT_MABL_APP_BASE_URL);
    +        return validateForm(restApiKeyName, environmentId, applicationId, false, job);
         }
     
         /**
    @@ -45,17 +44,14 @@ public static FormValidation validateForm(
          * @param environmentId prospective environment identifier
          * @param applicationId prospective application identifier
          * @param disableSslVerification prospective flag to indicate if SSL verification should be disabled
    -     * @param apiBaseUrl base URL for API (not user-visible)
    -     * @param appBaseUrl base URL for the ap (not user-visible)
          * @return validation result
          */
         public static FormValidation validateForm(
                 String restApiKeyId,
                 String environmentId,
                 String applicationId,
                 boolean disableSslVerification,
    -            String apiBaseUrl,
    -            String appBaseUrl
    +            Job job
         ) {
             try {
     
    @@ -90,8 +86,7 @@ public static FormValidation validateForm(
                     client = createMablRestApiClient(
                             restApiKeyClean,
                             disableSslVerification,
    -                        apiBaseUrl,
    -                        appBaseUrl
    +                        job
                     );
                     client.checkConnection();
                 } catch (IOException e) {
    @@ -103,4 +98,4 @@ public static FormValidation validateForm(
                 return error("Client error : " + e.getMessage());
             }
         }
    -}
    \ No newline at end of file
    +}
    
  • src/main/resources/com/mabl/integration/jenkins/MablStepBuilder/config.jelly+4 8 modified
    @@ -2,7 +2,9 @@
     <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:c="/lib/credentials" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
     
       <f:entry title="${%API Key Secret}" field="restApiKeyId">
    -      <c:select includeUser="true"/>
    +      <c:select includeUser="true">
    +        <option value="">- none -</option>
    +      </c:select>
       </f:entry>
     
       <f:entry title="${%Application}" field="applicationId">
    @@ -30,13 +32,7 @@
           <f:checkbox default="false" />
         </f:entry>
       </f:advanced>
    -  <f:invisibleEntry>
    -    <f:textbox field="apiBaseUrl" default="https://api.mabl.com"/>
    -  </f:invisibleEntry>
    -  <f:invisibleEntry>
    -    <f:textbox field="appBaseUrl" default="https://app.mabl.com"/>
    -  </f:invisibleEntry>
       <f:validateButton
           title="${%Validate}" progress="${%Validating...}"
    -      method="validateForm" with="restApiKeyId,applicationId,environmentId,disableSslVerification,apiBaseUrl,appBaseUrl" />
    +      method="validateForm" with="restApiKeyId,applicationId,environmentId,disableSslVerification" />
     </j:jelly>
    
  • src/test/java/com/mabl/integration/jenkins/MablStepDescriptorTest.java+72 24 modified
    @@ -7,27 +7,33 @@
     import hudson.util.FormValidation;
     import hudson.util.ListBoxModel;
     import org.junit.Before;
    +import org.junit.Rule;
     import org.junit.Test;
    -import org.mockito.MockedStatic;
    +import org.jvnet.hudson.test.JenkinsRule;
     
     import java.util.Collection;
     
     import static com.cloudbees.plugins.credentials.CredentialsProvider.USE_ITEM;
     import static org.junit.Assert.assertNotEquals;
    +import static org.junit.Assert.assertThrows;
     import static org.mockito.Mockito.doNothing;
     import static org.mockito.Mockito.mock;
     import static org.mockito.Mockito.when;
    -import static org.mockito.Mockito.mockStatic;
     import static org.junit.Assert.assertEquals;
     
     public class MablStepDescriptorTest {
     
         private MablStepBuilder.MablStepDescriptor mablStepDescriptor;
     
    +    @Rule
    +    public JenkinsRule j = new JenkinsRule();
    +
         @Before
         public void setup() {
             mablStepDescriptor = mock(MablStepBuilder.MablStepDescriptor.class);
             doNothing().when(mablStepDescriptor).load();
    +        // create a dummy security realm
    +        j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
         }
     
         @Test
    @@ -146,60 +152,102 @@ public void testCheckMablBranch_InValid()
         @Test
         public void testDoFillApplicationIdItems_NoApiKey()
         {
    +        Item item = new AbstractItem(null, "testItem") {
    +            @Override
    +            public Collection<? extends Job> getAllJobs() {
    +                return null;
    +            }
    +
    +            @Override
    +            public boolean hasPermission(Permission permission) {
    +                return true;
    +            }
    +        };
    +
             when(mablStepDescriptor.doFillApplicationIdItems(
    -                null, false, "https://api.mabl.com", "https://app.mabl.com")).thenCallRealMethod();
    -        ListBoxModel model = mablStepDescriptor.doFillApplicationIdItems(null, false, "https://api.mabl.com", "https://app.mabl.com");
    +                null, false, item)).thenCallRealMethod();
    +        ListBoxModel model = mablStepDescriptor.doFillApplicationIdItems(null, false, item);
             assertEquals(1, model.size());
             assertEquals("Select a valid API key", model.iterator().next().value);
     
             when(mablStepDescriptor.doFillApplicationIdItems(
    -                "", false, "https://api.mabl.com", "https://app.mabl.com")).thenCallRealMethod();
    -        model = mablStepDescriptor.doFillApplicationIdItems("", false, "https://api.mabl.com", "https://app.mabl.com");
    +                "", false, item)).thenCallRealMethod();
    +        model = mablStepDescriptor.doFillApplicationIdItems("", false, item);
             assertEquals(1, model.size());
             assertEquals("Select a valid API key", model.iterator().next().value);
         }
     
         @Test
         public void testDoFillApplicationIdItems_NoSecret()
         {
    -        when(mablStepDescriptor.doFillApplicationIdItems(
    -                "invalid-key", false, "https://api.mabl.com", "https://app.mabl.com")).thenCallRealMethod();
    +        Item item = new AbstractItem(null, "testItem") {
    +            @Override
    +            public Collection<? extends Job> getAllJobs() {
    +                return null;
    +            }
     
    -        try (MockedStatic<MablStepBuilder> mocked = mockStatic(MablStepBuilder.class)) {
    -            mocked.when(() -> MablStepBuilder.getRestApiSecret("invalid-key")).thenReturn(null);
    +            @Override
    +            public boolean hasPermission(Permission permission) {
    +                return true;
    +            }
    +        };
    +
    +        when(mablStepDescriptor.doFillApplicationIdItems(
    +                "invalid-key", false, item)).thenCallRealMethod();
     
    -            ListBoxModel model = mablStepDescriptor.doFillApplicationIdItems("invalid-key", false, "https://api.mabl.com", "https://app.mabl.com");
    -            assertEquals(0, model.size());
    -        }
    +        assertThrows(
    +            IllegalStateException.class,
    +            () -> mablStepDescriptor.doFillApplicationIdItems("invalid-key", false, item));
         }
     
         @Test
         public void testDoFillEnvironmentIdItems_NoApiKey() {
    +        Item item = new AbstractItem(null, "testItem") {
    +            @Override
    +            public Collection<? extends Job> getAllJobs() {
    +                return null;
    +            }
    +
    +            @Override
    +            public boolean hasPermission(Permission permission) {
    +                return true;
    +            }
    +        };
    +
             when(mablStepDescriptor.doFillEnvironmentIdItems(
    -                null, false, "https://api.mabl.com", "https://app.mabl.com")).thenCallRealMethod();
    -        ListBoxModel model = mablStepDescriptor.doFillEnvironmentIdItems(null, false, "https://api.mabl.com", "https://app.mabl.com");
    +                null, false, item)).thenCallRealMethod();
    +        ListBoxModel model = mablStepDescriptor.doFillEnvironmentIdItems(null, false, item);
             assertEquals(1, model.size());
             assertEquals("Select a valid API key", model.iterator().next().value);
     
             when(mablStepDescriptor.doFillEnvironmentIdItems(
    -                "", false, "https://api.mabl.com", "https://app.mabl.com")).thenCallRealMethod();
    -        model = mablStepDescriptor.doFillEnvironmentIdItems("", false, "https://api.mabl.com", "https://app.mabl.com");
    +                "", false, item)).thenCallRealMethod();
    +        model = mablStepDescriptor.doFillEnvironmentIdItems("", false, item);
             assertEquals(1, model.size());
             assertEquals("Select a valid API key", model.iterator().next().value);
         }
     
         @Test
         public void testDoFillEnvironmentIdItems_NoSecret()
         {
    -        when(mablStepDescriptor.doFillEnvironmentIdItems(
    -                "invalid-key", false, "https://api.mabl.com", "https://app.mabl.com")).thenCallRealMethod();
    +        Item item = new AbstractItem(null, "testItem") {
    +            @Override
    +            public Collection<? extends Job> getAllJobs() {
    +                return null;
    +            }
    +
    +            @Override
    +            public boolean hasPermission(Permission permission) {
    +                return true;
    +            }
    +        };
     
    -        try (MockedStatic<MablStepBuilder> mocked = mockStatic(MablStepBuilder.class)) {
    -            mocked.when(() -> MablStepBuilder.getRestApiSecret("invalid-key")).thenReturn(null);
    +        when(mablStepDescriptor.doFillEnvironmentIdItems(
    +                "invalid-key", false, item)).thenCallRealMethod();
     
    -            ListBoxModel model = mablStepDescriptor.doFillEnvironmentIdItems("invalid-key", false, "https://api.mabl.com", "https://app.mabl.com");
    -            assertEquals(0, model.size());
    -        }
    +        assertThrows(
    +            IllegalStateException.class,
    +            () -> mablStepDescriptor.doFillEnvironmentIdItems("invalid-key", false, item));
         }
     
         @Test
    
  • src/test/java/com/mabl/integration/jenkins/validation/MablBuildValidatorTest.java+32 22 modified
    @@ -2,18 +2,19 @@
     
     import com.mabl.integration.jenkins.MablRestApiClient;
     import com.mabl.integration.jenkins.MablStepBuilder;
    -import com.mabl.integration.jenkins.MablStepConstants;
     import com.mabl.integration.jenkins.domain.CreateDeploymentProperties;
     import com.mabl.integration.jenkins.domain.CreateDeploymentResult;
     import com.mabl.integration.jenkins.domain.ExecutionResult;
     import com.mabl.integration.jenkins.domain.GetApiKeyResult;
     import com.mabl.integration.jenkins.domain.GetApplicationsResult;
     import com.mabl.integration.jenkins.domain.GetEnvironmentsResult;
    -import com.mabl.integration.jenkins.domain.GetEnvironmentsResultTest;
     import com.mabl.integration.jenkins.domain.GetLabelsResult;
    -import com.mabl.integration.jenkins.domain.GetLabelsResultTest;
    +import hudson.model.FreeStyleProject;
     import hudson.util.FormValidation;
    +import org.junit.Before;
    +import org.junit.Rule;
     import org.junit.Test;
    +import org.jvnet.hudson.test.JenkinsRule;
     import org.mockito.MockedStatic;
     
     import java.io.IOException;
    @@ -32,17 +33,26 @@
      * Unit test runner
      */
     public class MablBuildValidatorTest {
    +    FreeStyleProject freeStyleProject;
    +
    +    @Rule
    +    public JenkinsRule jenkinsRule = new JenkinsRule();
    +
    +    @Before
    +    public void setupTest() {
    +        freeStyleProject = jenkinsRule.jenkins.getItemByFullName("my-freestyle", FreeStyleProject.class);
    +    }
     
         @Test
         public void validateGoodAllFieldsForm() {
    -
             try (MockedStatic<MablStepBuilder> mocked = mockStatic(MablStepBuilder.class)) {
                 mockNoopRestApiClient(mocked);
     
                 final FormValidation actual = validateForm(
                         "sample-key-id",
                         "sample-environment-id",
    -                    "sample-application-id"
    +                    "sample-application-id",
    +                     freeStyleProject
                 );
     
                 assertEquals(actual.kind, OK);
    @@ -51,14 +61,14 @@ public void validateGoodAllFieldsForm() {
     
         @Test
         public void validateGoodEnvironmentOnlyForm() {
    -
             try (MockedStatic<MablStepBuilder> mocked = mockStatic(MablStepBuilder.class)) {
                 mockNoopRestApiClient(mocked);
     
                 final FormValidation actual = validateForm(
                         "sample-key-id",
                         "sample-environment-id",
    -                    null
    +                    null,
    +                     freeStyleProject
                 );
     
                 assertEquals(actual.kind, OK);
    @@ -68,13 +78,13 @@ public void validateGoodEnvironmentOnlyForm() {
     
         @Test
         public void validateGoodApplicationOnlyForm() {
    -
             try (MockedStatic<MablStepBuilder> mocked = mockStatic(MablStepBuilder.class)) {
                 mockNoopRestApiClient(mocked);
                 final FormValidation actual = validateForm(
                         "sample-key-id",
                         null,
    -                    "sample-application-id"
    +                    "sample-application-id",
    +                     freeStyleProject
                 );
     
                 assertEquals(actual.kind, OK);
    @@ -83,11 +93,11 @@ public void validateGoodApplicationOnlyForm() {
     
         @Test
         public void validateBadNoEnvironmentOrApplicationForm() {
    -
             final FormValidation actual = validateForm(
                     "sample-key-id",
                     null,
    -                null
    +                null,
    +                 freeStyleProject
             );
     
             assertEquals(ERROR, actual.kind);
    @@ -99,11 +109,11 @@ public void validateBadNoEnvironmentOrApplicationForm() {
     
         @Test
         public void validateBadNoEnvironmentIdInWrongFieldApplicationForm() {
    -
             final FormValidation actual = validateForm(
                     "sample-key-id",
                     "sample-a",
    -                null
    +                null,
    +                 freeStyleProject
             );
     
             assertEquals(ERROR, actual.kind);
    @@ -115,11 +125,11 @@ public void validateBadNoEnvironmentIdInWrongFieldApplicationForm() {
     
         @Test
         public void validateBadNoApplicationIdInWrongFieldApplicationForm() {
    -
             final FormValidation actual = validateForm(
                     "sample-key-id",
                     null,
    -                "sample-e"
    +                "sample-e",
    +                 freeStyleProject
             );
     
             assertEquals(ERROR, actual.kind);
    @@ -131,11 +141,11 @@ public void validateBadNoApplicationIdInWrongFieldApplicationForm() {
     
         @Test
         public void validateBadNoEnvironmentOrApplicationWhiteSpaceForm() {
    -
             final FormValidation actual = validateForm(
                     "sample-key-id",
                     "  ",
    -                "\t"
    +                "\t",
    +                 freeStyleProject
             );
     
             assertEquals(ERROR, actual.kind);
    @@ -147,11 +157,11 @@ public void validateBadNoEnvironmentOrApplicationWhiteSpaceForm() {
     
         @Test
         public void validateBadNoRestApiKeyForm() {
    -
             final FormValidation actual = validateForm(
                     null,
                     null,
    -                null
    +                null,
    +                 freeStyleProject
             );
     
             assertEquals(ERROR, actual.kind);
    @@ -161,11 +171,11 @@ public void validateBadNoRestApiKeyForm() {
     
         @Test
         public void validateBadRestApiKey() {
    -
             final FormValidation actual = validateForm(
                     "key:invalid-key",
                     null,
    -                null
    +                null,
    +                 freeStyleProject
             );
     
             assertEquals(ERROR, actual.kind);
    @@ -175,7 +185,7 @@ public void validateBadRestApiKey() {
     
         private void mockNoopRestApiClient(MockedStatic<MablStepBuilder> mocked) {
             mocked.when(() -> MablStepBuilder.createMablRestApiClient(
    -                "sample-key-id", false, MablStepConstants.DEFAULT_MABL_API_BASE_URL, MablStepConstants.DEFAULT_MABL_APP_BASE_URL)).thenReturn(
    +                "sample-key-id", false, freeStyleProject)).thenReturn(
                     new MablRestApiClient() {
                         @Override
                         public CreateDeploymentResult createDeploymentEvent(String environmentId, String applicationId, String labels, String mablBranch, CreateDeploymentProperties properties) throws IOException {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

1