CVE-2025-64132
Description
Jenkins MCP Server Plugin 0.84.v50ca_24ef83f2 and earlier does not perform permission checks in multiple MCP tools, allowing attackers to trigger builds and obtain information about job and cloud configuration they should not be able to access.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Jenkins MCP Server Plugin 0.84.v50ca_24ef83f2 and earlier lacks permission checks in multiple MCP tools, allowing unauthorized job builds and access to SCM and cloud configuration.
Vulnerability
Overview
The Jenkins MCP Server Plugin, which implements the Model Context Protocol to expose Jenkins functionalities as MCP tools, contains a missing authorization vulnerability. In versions 0.84.v50ca_24ef83f2 and earlier, several MCP tools do not perform the required permission checks before executing operations [1][2]. This flaw stems from insufficient validation of user permissions within the plugin's tool implementations.
Exploitation
Prerequisites
An attacker can exploit these missing checks from any MCP client capable of communicating with the Jenkins MCP server. The attack surface is accessible to users who have at least basic access to Jenkins. Specifically, an attacker with only Item/Read permission can obtain the configured SCM details of a job (bypassing Item/Extended Read) and can trigger new builds without Item/Build permission [2]. Additionally, an attacker without Overall/Read permission can retrieve names of configured clouds [2].
Impact
Successful exploitation allows an attacker to gain sensitive information about job SCM configurations and cloud names, and to initiate builds on jobs they should not be authorized to build [3]. This can lead to unauthorized code execution (if builds trigger scripts) and exposure of infrastructure details, potentially enabling further attacks within the Jenkins environment.
Mitigation
Status
The vulnerability is fixed in MCP Server Plugin version 0.86.v7d3355e6a_a_18, which adds proper permission checks for the affected MCP tools [2][3]. Users should upgrade immediately. There is no indication of exploitation in the wild; however, given the plugin's exposure of Jenkins functions, applying the patch is strongly recommended.
AI Insight generated on May 19, 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.
| Package | Affected versions | Patched versions |
|---|---|---|
io.jenkins.plugins:mcp-serverMaven | < 0.86.v7d3355e6a | 0.86.v7d3355e6a |
Affected products
2- Range: <=0.84.v50ca_24ef83f2
- Jenkins Project/Jenkins MCP Server Pluginv5Range: 0
Patches
159de6a268b4c[SECURITY-3622] Missing permission checks (#58)
4 files changed · +63 −27
src/main/java/io/jenkins/plugins/mcp/server/extensions/DefaultMcpServer.java+9 −6 modified@@ -90,6 +90,7 @@ public boolean triggerBuild( var job = Jenkins.get().getItemByFullName(jobFullName, ParameterizedJobMixIn.ParameterizedJob.class); if (job != null) { + job.checkPermission(Item.BUILD); if (job.isParameterized() && job instanceof Job j) { ParametersDefinitionProperty parametersDefinition = (ParametersDefinitionProperty) j.getProperty(ParametersDefinitionProperty.class); @@ -232,12 +233,14 @@ public Map<String, Object> getStatus() { map.put("Buildable Queue Size", queue.countBuildableItems()); map.put("Available executors (any label)", availableExecutors); // Tell me which clouds are defined as they can be used to provision ephemeral agents - map.put( - "Defined clouds that can provide agents (any label)", - jenkins.clouds.stream() - .filter(cloud -> cloud.canProvision(new Cloud.CloudState(null, 1))) - .map(Cloud::getDisplayName) - .toList()); + if (Jenkins.get().hasAnyPermission(Jenkins.SYSTEM_READ)) { + map.put( + "Defined clouds that can provide agents (any label)", + jenkins.clouds.stream() + .filter(cloud -> cloud.canProvision(new Cloud.CloudState(null, 1))) + .map(Cloud::getDisplayName) + .toList()); + } // getActiveAdministrativeMonitors is already protected, so no need to check the user map.put( "Active administrative monitors",
src/main/java/io/jenkins/plugins/mcp/server/extensions/JobScmExtension.java+13 −10 modified@@ -29,6 +29,7 @@ import static io.jenkins.plugins.mcp.server.extensions.util.JenkinsUtil.getBuildByNumberOrLast; import hudson.Extension; +import hudson.model.Item; import hudson.model.Job; import io.jenkins.plugins.mcp.server.McpServerExtension; import io.jenkins.plugins.mcp.server.annotation.Tool; @@ -56,16 +57,18 @@ public List getJobScm( @ToolParam(description = "Full path of the Jenkins job (e.g., 'folder/job-name')") String jobFullName) { var job = Jenkins.get().getItemByFullName(jobFullName, Job.class); if (job instanceof SCMTriggerItem scmItem) { - return scmItem.getSCMs().stream() - .map(scm -> { - Object result = null; - if (scm.getType().equals("hudson.plugins.git.GitSCM")) { - result = GitScmUtil.extractGitScmInfo(scm); - } - return result; - }) - .filter(Objects::nonNull) - .toList(); + if (job.hasPermission(Item.EXTENDED_READ)) { + return scmItem.getSCMs().stream() + .map(scm -> { + Object result = null; + if (scm.getType().equals("hudson.plugins.git.GitSCM")) { + result = GitScmUtil.extractGitScmInfo(scm); + } + return result; + }) + .filter(Objects::nonNull) + .toList(); + } } return List.of(); }
src/main/java/io/jenkins/plugins/mcp/server/tool/McpToolWrapper.java+1 −0 modified@@ -237,6 +237,7 @@ McpSchema.CallToolResult toMcpResult(Object result) { McpSchema.CallToolResult callRequest(McpSyncServerExchange exchange, McpSchema.CallToolRequest request) { var args = request.arguments(); var oldUser = User.current(); + try { var user = tryGetUser(exchange, request); if (user != null) {
src/test/java/io/jenkins/plugins/mcp/server/extensions/DefaultMcpServerTest.java+40 −11 modified@@ -93,13 +93,45 @@ void testMcpToolCallGetBuild(JenkinsRule jenkins, JenkinsMcpClientBuilder jenkin } } - @McpClientTest - void testMcpToolCallTriggerBuild(JenkinsRule jenkins, JenkinsMcpClientBuilder jenkinsMcpClientBuilder) + static Stream<Arguments> triggerSecurityTestParameters() { + Stream<Arguments> baseArgs = Stream.of( + // security enable, no auth -> no run triggered + Arguments.of(true, false, false), + // security enable, auth -> no triggered + Arguments.of(true, true, true), + // security not enable, no auth -> run triggered yeah freedom! + Arguments.of(false, false, true), + // security not enable, auth -> run triggered root is the king + Arguments.of(false, true, true)); + return TestUtils.appendMcpClientArgs(baseArgs); + } + + @ParameterizedTest + @MethodSource("triggerSecurityTestParameters") + void testMcpToolCallTriggerBuild( + Boolean enableSecurity, + Boolean runAsAdmin, + Boolean expectedBuildRunned, + JenkinsMcpClientBuilder jenkinsMcpClientBuilder, + JenkinsRule jenkins) throws Exception { + if (enableSecurity) { + enableSecurity(jenkins); + } WorkflowJob project = jenkins.createProject(WorkflowJob.class, "demo-job"); project.setDefinition(new CpsFlowDefinition("", true)); - var nextNumber = project.getNextBuildNumber(); - try (var client = jenkinsMcpClientBuilder.jenkins(jenkins).build()) { + try (var client = jenkinsMcpClientBuilder + .jenkins(jenkins) + .requestCustomizer(request -> { + if (runAsAdmin) { + String username = "admin"; + String password = "admin"; + String authString = username + ":" + password; + String encodedAuth = Base64.getEncoder().encodeToString(authString.getBytes()); + request.setHeader("Authorization", "Basic " + encodedAuth); + } + }) + .build()) { McpSchema.CallToolRequest request = new McpSchema.CallToolRequest("triggerBuild", Map.of("jobFullName", project.getFullName())); @@ -109,12 +141,12 @@ void testMcpToolCallTriggerBuild(JenkinsRule jenkins, JenkinsMcpClientBuilder je assertThat(response.content().get(0).type()).isEqualTo("text"); assertThat(response.content()).first().isInstanceOfSatisfying(McpSchema.TextContent.class, textContent -> { assertThat(textContent.type()).isEqualTo("text"); - assertThat(textContent.text()).contains("true"); + assertThat(textContent.text()).contains(Boolean.toString(expectedBuildRunned)); }); + if (!expectedBuildRunned) { + assertThat(project.getLastBuild()).isNull(); + } } - await().atMost(10, SECONDS).until(() -> project.getLastBuild() != null); - jenkins.waitForCompletion(project.getLastBuild()); - await().atMost(10, SECONDS).until(() -> project.getLastBuild().getNumber() == nextNumber); } @McpClientTest @@ -380,12 +412,9 @@ void testMcpToolCallGetStatusWithAuth(JenkinsRule jenkins, JenkinsMcpClientBuild private void enableSecurity(JenkinsRule jenkins) throws Exception { JenkinsRule.DummySecurityRealm securityRealm = jenkins.createDummySecurityRealm(); jenkins.jenkins.setSecurityRealm(securityRealm); - // Create a user - var authStrategy = new FullControlOnceLoggedInAuthorizationStrategy(); authStrategy.setAllowAnonymousRead(false); jenkins.jenkins.setAuthorizationStrategy(authStrategy); - jenkins.jenkins.save(); } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-mrpq-9jr3-rqq9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-64132ghsaADVISORY
- www.jenkins.io/security/advisory/2025-10-29/ghsavendor-advisoryWEB
- www.openwall.com/lists/oss-security/2025/10/29/2ghsaWEB
- github.com/jenkinsci/mcp-server-plugin/commit/59de6a268b4c6844a3a9c6c55a541de183e71a97ghsaWEB
News mentions
1- Jenkins Security Advisory 2025-10-29Jenkins Security Advisories · Oct 29, 2025