VYPR
Moderate severityNVD Advisory· Published Mar 5, 2025· Updated Mar 6, 2025

CVE-2025-27622

CVE-2025-27622

Description

Jenkins 2.499 and earlier, LTS 2.492.1 and earlier does not redact encrypted values of secrets when accessing config.xml of agents via REST API or CLI, allowing attackers with Agent/Extended Read permission to view encrypted values of secrets.

AI Insight

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

Jenkins 2.499 and earlier, LTS 2.492.1 and earlier fails to redact encrypted secrets in agent config.xml via REST API/CLI, allowing Agent/Extended Read users to view them.

Vulnerability

Overview

CVE-2025-27622 is a medium-severity vulnerability in Jenkins (core) versions 2.499 and earlier, and LTS 2.492.1 and earlier. The Jenkins security advisory describes that the software does not redact encrypted values of secrets when accessing config.xml of agents via REST API or CLI [1][2][3]. The root cause is a missing redaction mechanism for the agent configuration endpoint, which the patch addresses by applying ExtendedReadSecretRedaction to the agent config.xml output [1].

Exploitation

Scenario

An attacker must have the Agent/Extended Read permission to exploit this vulnerability. With that permission, the attacker can retrieve the agent's config.xml through the REST API or CLI and view the encrypted values of secrets stored within it [3]. No additional authentication or network position is required beyond the granted permission. The vulnerability affects both the weekly and LTS release lines [2].

Impact

Successful exploitation allows the attacker to view encrypted secrets, such as credentials used for agent connections. While the secrets are encrypted, exposure of the encrypted values may aid in offline brute-force attacks or other cryptographic analysis, potentially leading to credential disclosure. The Jenkins advisory assigns a CVSS score of Medium for this vulnerability [3].

Mitigation

Jenkins has fixed this issue in versions 2.500 and LTS 2.492.2, which redact the encrypted values of secrets stored in agent config.xml when accessed via REST API or CLI by users lacking the Agent/Configure permission [3]. Users should upgrade to these patched versions. The fix is part of a larger security update that also addresses related issues (CVE-2025-27623 and CVE-2025-27624) in the same advisory [3].

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
org.jenkins-ci.main:jenkins-coreMaven
>= 2.493, < 2.5002.500
org.jenkins-ci.main:jenkins-coreMaven
< 2.492.22.492.2

Affected products

5

Patches

1
923cdbc165e8

[SECURITY-3495][SECURITY-3496]

https://github.com/jenkinsci/jenkinsDaniel BeckFeb 25, 2025via ghsa
5 files changed · +204 4
  • core/src/main/java/hudson/cli/GetNodeCommand.java+13 1 modified
    @@ -27,8 +27,11 @@
     import hudson.Extension;
     import hudson.model.Computer;
     import hudson.model.Node;
    +import java.io.ByteArrayOutputStream;
     import java.io.IOException;
    +import java.nio.charset.StandardCharsets;
     import jenkins.model.Jenkins;
    +import jenkins.security.ExtendedReadRedaction;
     import org.kohsuke.args4j.Argument;
     
     /**
    @@ -52,7 +55,16 @@ protected int run() throws IOException {
     
             node.checkPermission(Computer.EXTENDED_READ);
     
    -        Jenkins.XSTREAM2.toXMLUTF8(node, stdout);
    +        if (node.hasPermission(Computer.CONFIGURE)) {
    +            Jenkins.XSTREAM2.toXMLUTF8(node, stdout);
    +        } else {
    +            var baos = new ByteArrayOutputStream();
    +            Jenkins.XSTREAM2.toXMLUTF8(node, baos);
    +            String xml = baos.toString(StandardCharsets.UTF_8);
    +
    +            xml = ExtendedReadRedaction.applyAll(xml);
    +            org.apache.commons.io.IOUtils.write(xml, stdout, StandardCharsets.UTF_8);
    +        }
     
             return 0;
         }
    
  • core/src/main/java/hudson/cli/GetViewCommand.java+14 1 modified
    @@ -26,6 +26,9 @@
     
     import hudson.Extension;
     import hudson.model.View;
    +import java.io.ByteArrayOutputStream;
    +import java.nio.charset.StandardCharsets;
    +import jenkins.security.ExtendedReadRedaction;
     import org.kohsuke.args4j.Argument;
     
     /**
    @@ -48,7 +51,17 @@ public String getShortDescription() {
         protected int run() throws Exception {
     
             view.checkPermission(View.READ);
    -        view.writeXml(stdout);
    +
    +        if (view.hasPermission(View.CONFIGURE)) {
    +            view.writeXml(stdout);
    +        } else {
    +            var baos = new ByteArrayOutputStream();
    +            view.writeXml(baos);
    +            String xml = baos.toString(StandardCharsets.UTF_8);
    +
    +            xml = ExtendedReadRedaction.applyAll(xml);
    +            org.apache.commons.io.IOUtils.write(xml, stdout, StandardCharsets.UTF_8);
    +        }
     
             return 0;
         }
    
  • core/src/main/java/hudson/model/Computer.java+13 1 modified
    @@ -74,6 +74,7 @@
     import hudson.util.RemotingDiagnostics.HeapDump;
     import hudson.util.RunList;
     import jakarta.servlet.ServletException;
    +import java.io.ByteArrayOutputStream;
     import java.io.File;
     import java.io.IOException;
     import java.io.InputStream;
    @@ -83,6 +84,7 @@
     import java.net.InetAddress;
     import java.net.NetworkInterface;
     import java.nio.charset.Charset;
    +import java.nio.charset.StandardCharsets;
     import java.nio.file.Files;
     import java.nio.file.InvalidPathException;
     import java.nio.file.StandardCopyOption;
    @@ -110,6 +112,7 @@
     import jenkins.model.IComputer;
     import jenkins.model.IDisplayExecutor;
     import jenkins.model.Jenkins;
    +import jenkins.security.ExtendedReadRedaction;
     import jenkins.security.ImpersonatingExecutorService;
     import jenkins.security.MasterToSlaveCallable;
     import jenkins.security.stapler.StaplerDispatchable;
    @@ -1526,7 +1529,16 @@ public void doConfigDotXml(StaplerRequest2 req, StaplerResponse2 rsp)
                 if (node == null) {
                     throw HttpResponses.notFound();
                 }
    -            Jenkins.XSTREAM2.toXMLUTF8(node, rsp.getOutputStream());
    +            if (hasPermission(CONFIGURE)) {
    +                Jenkins.XSTREAM2.toXMLUTF8(node, rsp.getOutputStream());
    +            } else {
    +                var baos = new ByteArrayOutputStream();
    +                Jenkins.XSTREAM2.toXMLUTF8(node, baos);
    +                String xml = baos.toString(StandardCharsets.UTF_8);
    +
    +                xml = ExtendedReadRedaction.applyAll(xml);
    +                org.apache.commons.io.IOUtils.write(xml, rsp.getOutputStream(), StandardCharsets.UTF_8);
    +            }
                 return;
             }
             if (req.getMethod().equals("POST")) {
    
  • core/src/main/java/hudson/model/View.java+12 1 modified
    @@ -62,6 +62,7 @@
     import jakarta.servlet.http.HttpServletResponse;
     import java.io.BufferedInputStream;
     import java.io.ByteArrayInputStream;
    +import java.io.ByteArrayOutputStream;
     import java.io.IOException;
     import java.io.InputStream;
     import java.io.OutputStream;
    @@ -94,6 +95,7 @@
     import jenkins.model.item_category.Categories;
     import jenkins.model.item_category.Category;
     import jenkins.model.item_category.ItemCategory;
    +import jenkins.security.ExtendedReadRedaction;
     import jenkins.security.stapler.StaplerNotDispatchable;
     import jenkins.util.xml.XMLUtils;
     import jenkins.widgets.HasWidgets;
    @@ -983,7 +985,16 @@ private HttpResponse doConfigDotXmlImpl(StaplerRequest2 req) throws IOException
                     @Override
                     public void generateResponse(StaplerRequest2 req, StaplerResponse2 rsp, Object node) throws IOException, ServletException {
                         rsp.setContentType("application/xml");
    -                    View.this.writeXml(rsp.getOutputStream());
    +                    if (hasPermission(CONFIGURE)) {
    +                        View.this.writeXml(rsp.getOutputStream());
    +                    } else {
    +                        var baos = new ByteArrayOutputStream();
    +                        View.this.writeXml(baos);
    +                        String xml = baos.toString(StandardCharsets.UTF_8);
    +
    +                        xml = ExtendedReadRedaction.applyAll(xml);
    +                        org.apache.commons.io.IOUtils.write(xml, rsp.getOutputStream(), StandardCharsets.UTF_8);
    +                    }
                     }
                 };
             }
    
  • test/src/test/java/lib/form/PasswordTest.java+152 0 modified
    @@ -24,6 +24,7 @@
     
     package lib.form;
     
    +import static java.nio.file.Files.readString;
     import static org.hamcrest.MatcherAssert.assertThat;
     import static org.hamcrest.Matchers.containsString;
     import static org.hamcrest.Matchers.is;
    @@ -40,6 +41,8 @@
     import hudson.Launcher;
     import hudson.cli.CopyJobCommand;
     import hudson.cli.GetJobCommand;
    +import hudson.cli.GetNodeCommand;
    +import hudson.cli.GetViewCommand;
     import hudson.model.AbstractProject;
     import hudson.model.Action;
     import hudson.model.Computer;
    @@ -48,25 +51,35 @@
     import hudson.model.Job;
     import hudson.model.JobProperty;
     import hudson.model.JobPropertyDescriptor;
    +import hudson.model.ListView;
    +import hudson.model.Node;
     import hudson.model.RootAction;
     import hudson.model.Run;
     import hudson.model.TaskListener;
     import hudson.model.User;
    +import hudson.model.View;
    +import hudson.model.ViewProperty;
    +import hudson.security.ACL;
    +import hudson.slaves.DumbSlave;
    +import hudson.slaves.NodeProperty;
     import hudson.tasks.BuildStepDescriptor;
     import hudson.tasks.Builder;
     import hudson.util.FormValidation;
     import hudson.util.Secret;
     import java.io.ByteArrayOutputStream;
    +import java.io.File;
     import java.io.IOException;
     import java.io.PrintStream;
     import java.util.Arrays;
     import java.util.Collection;
     import java.util.List;
     import java.util.Locale;
    +import java.util.Map;
     import java.util.regex.Pattern;
     import jenkins.model.GlobalConfiguration;
     import jenkins.model.Jenkins;
     import jenkins.model.TransientActionFactory;
    +import jenkins.security.ExtendedReadRedaction;
     import jenkins.security.ExtendedReadSecretRedaction;
     import jenkins.tasks.SimpleBuildStep;
     import org.htmlunit.Page;
    @@ -124,6 +137,145 @@ public String getUrlName() {
             }
         }
     
    +    @For({ExtendedReadRedaction.class, ExtendedReadSecretRedaction.class})
    +    @Issue("SECURITY-3495")
    +    @Test
    +    public void testNodeSecrets() throws Exception {
    +        Computer.EXTENDED_READ.setEnabled(true);
    +        j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
    +        j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("alice").grant(Jenkins.READ, Computer.EXTENDED_READ).everywhere().to("bob"));
    +
    +        final DumbSlave onlineSlave = j.createOnlineSlave();
    +        final String secretText = "t0ps3cr3td4t4_node";
    +        final Secret encryptedSecret = Secret.fromString(secretText);
    +        final String encryptedSecretText = encryptedSecret.getEncryptedValue();
    +
    +        onlineSlave.getNodeProperties().add(new NodePropertyWithSecret(encryptedSecret));
    +        onlineSlave.save();
    +
    +        assertThat(readString(new File(onlineSlave.getRootDir(), "config.xml").toPath()), containsString(encryptedSecretText));
    +
    +
    +        { // admin can see encrypted value
    +            GetNodeCommand command = new GetNodeCommand();
    +            try (JenkinsRule.WebClient wc = j.createWebClient().login("alice")) {
    +                final Page page = wc.goTo(onlineSlave.getComputer().getUrl() + "config.xml", "application/xml");
    +                final String content = page.getWebResponse().getContentAsString();
    +
    +                assertThat(content, not(containsString(secretText)));
    +                assertThat(content, containsString(encryptedSecretText));
    +                assertThat(content, containsString("<secret>" + encryptedSecretText + "</secret>"));
    +
    +                var baos = new ByteArrayOutputStream();
    +                try (var unused = ACL.as(User.get("alice", true, Map.of()))) {
    +                    command.setTransportAuth2(Jenkins.getAuthentication2());
    +                    command.main(List.of(onlineSlave.getNodeName()), Locale.US, System.in, new PrintStream(baos), System.err);
    +                }
    +                assertEquals(content, baos.toString(page.getWebResponse().getContentCharset()));
    +            }
    +        }
    +
    +        { // extended reader gets only redacted value
    +            GetNodeCommand command = new GetNodeCommand();
    +            try (JenkinsRule.WebClient wc = j.createWebClient().login("bob")) {
    +                final Page page = wc.goTo(onlineSlave.getComputer().getUrl() + "config.xml", "application/xml");
    +                final String content = page.getWebResponse().getContentAsString();
    +
    +                assertThat(content, not(containsString(secretText)));
    +                assertThat(content, not(containsString(encryptedSecretText)));
    +                assertThat(content, containsString("<secret>********</secret>"));
    +
    +                var baos = new ByteArrayOutputStream();
    +                try (var unused = ACL.as(User.get("bob", true, Map.of()))) {
    +                    command.setTransportAuth2(Jenkins.getAuthentication2());
    +                    command.main(List.of(onlineSlave.getNodeName()), Locale.US, System.in, new PrintStream(baos), System.err);
    +                }
    +                assertEquals(content, baos.toString(page.getWebResponse().getContentCharset()));
    +            }
    +        }
    +    }
    +
    +    public static class NodePropertyWithSecret extends NodeProperty<Node> {
    +        private final Secret secret;
    +
    +        public NodePropertyWithSecret(Secret secret) {
    +            this.secret = secret;
    +        }
    +
    +        public Secret getSecret() {
    +            return secret;
    +        }
    +    }
    +
    +    @For({ExtendedReadRedaction.class, ExtendedReadSecretRedaction.class})
    +    @Issue("SECURITY-3496")
    +    @Test
    +    public void testViewSecrets() throws Exception {
    +        j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
    +        j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("alice").grant(Jenkins.READ, View.READ).everywhere().to("bob"));
    +
    +        final String secretText = "t0ps3cr3td4t4_view";
    +        final Secret encryptedSecret = Secret.fromString(secretText);
    +        final String encryptedSecretText = encryptedSecret.getEncryptedValue();
    +
    +        final ListView v = new ListView("security-3496");
    +        v.getProperties().add(new ViewPropertyWithSecret(encryptedSecret));
    +        j.jenkins.addView(v);
    +
    +        assertThat(readString(new File(j.jenkins.getRootDir(), "config.xml").toPath()), containsString(encryptedSecretText));
    +
    +
    +        { // admin can see encrypted value
    +            var command = new GetViewCommand();
    +            try (JenkinsRule.WebClient wc = j.createWebClient().login("alice")) {
    +                final Page page = wc.goTo(v.getUrl() + "config.xml", "application/xml");
    +                final String content = page.getWebResponse().getContentAsString();
    +
    +                assertThat(content, not(containsString(secretText)));
    +                assertThat(content, containsString(encryptedSecretText));
    +                assertThat(content, containsString("<secret>" + encryptedSecretText + "</secret>"));
    +
    +                var baos = new ByteArrayOutputStream();
    +                try (var unused = ACL.as(User.get("alice", true, Map.of()))) {
    +                    command.setTransportAuth2(Jenkins.getAuthentication2());
    +                    command.main(List.of(v.getViewName()), Locale.US, System.in, new PrintStream(baos), System.err);
    +                }
    +                assertEquals(content, baos.toString(page.getWebResponse().getContentCharset()));
    +            }
    +        }
    +
    +        { // extended reader gets only redacted value
    +            var command = new GetViewCommand();
    +            try (JenkinsRule.WebClient wc = j.createWebClient().login("bob")) {
    +                final Page page = wc.goTo(v.getUrl() + "config.xml", "application/xml");
    +                final String content = page.getWebResponse().getContentAsString();
    +
    +                assertThat(content, not(containsString(secretText)));
    +                assertThat(content, not(containsString(encryptedSecretText)));
    +                assertThat(content, containsString("<secret>********</secret>"));
    +
    +                var baos = new ByteArrayOutputStream();
    +                try (var unused = ACL.as(User.get("bob", true, Map.of()))) {
    +                    command.setTransportAuth2(Jenkins.getAuthentication2());
    +                    command.main(List.of(v.getViewName()), Locale.US, System.in, new PrintStream(baos), System.err);
    +                }
    +                assertEquals(content, baos.toString(page.getWebResponse().getContentCharset()));
    +            }
    +        }
    +    }
    +
    +    public static class ViewPropertyWithSecret extends ViewProperty {
    +        private final Secret secret;
    +
    +        public ViewPropertyWithSecret(Secret secret) {
    +            this.secret = secret;
    +        }
    +
    +        public Secret getSecret() {
    +            return secret;
    +        }
    +    }
    +
         @Issue({"SECURITY-266", "SECURITY-304"})
         @Test
         @For(ExtendedReadSecretRedaction.class)
    

Vulnerability mechanics

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

References

4

News mentions

1