VYPR
Moderate severityNVD Advisory· Published Oct 29, 2025· Updated Nov 4, 2025

CVE-2025-64132

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.

PackageAffected versionsPatched versions
io.jenkins.plugins:mcp-serverMaven
< 0.86.v7d3355e6a0.86.v7d3355e6a

Affected products

2

Patches

1
59de6a268b4c

[SECURITY-3622] Missing permission checks (#58)

https://github.com/jenkinsci/mcp-server-pluginOlivier LamySep 25, 2025via ghsa
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

News mentions

1