VYPR
Moderate severityNVD Advisory· Published Sep 3, 2025· Updated Nov 4, 2025

CVE-2025-58458

CVE-2025-58458

Description

In Jenkins Git client Plugin 6.3.2 and earlier, except 6.1.4 and 6.2.1, Git URL field form validation responses differ based on whether the specified file path exists on the controller when specifying amazon-s3 protocol for use with JGit, allowing attackers with Overall/Read permission to check for the existence of an attacker-specified file path on the Jenkins controller file system.

AI Insight

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

Jenkins Git client Plugin 6.3.2 and earlier allows attackers with Overall/Read permission to enumerate file paths on the controller via amazon-s3 protocol form validation.

Vulnerability

Description The Jenkins Git client Plugin [1] includes an experimental amazon-s3 protocol for use with the bundled JGit library. This protocol authenticates against Amazon S3 based on the contents of a file whose path is provided as the authority part of the URL (e.g., amazon-s3://path-to-file@bucketname/folder). Due to a bug, any attempt to use this protocol to perform git operations always fails, but the error messages differ depending on whether the specified file path exists on the Jenkins controller [4]. This information disclosure vulnerability (CVE-2025-58458) affects versions 6.3.2 and earlier, except 6.1.4 and 6.2.1 [3].

Exploitation

Attackers with Overall/Read permission can exploit this by submitting a Git URL field form validation request with an amazon-s3 URL pointing to an arbitrary file path on the controller. The response indicates whether the file exists, enabling file path enumeration [4]. The attack requires that the plugin is configured to use JGit (not the default command-line git) and that the attacker has access to a form field that triggers Git URL validation. For example, attackers with Credentials/Use Item permission (implied by Item/Configure) can use URL fields in Git Plugin [4].

Impact

Successful exploitation allows an attacker to check for the existence of specific file paths on the Jenkins controller file system. This can aid in reconnaissance for further attacks, such as identifying the presence of sensitive files or configuration files. The vulnerability does not allow reading file contents, only existence checking [4].

Mitigation

Jenkins Git client Plugin version 6.3.3 prohibits use of the amazon-s3 protocol with JGit, fixing the issue [2]. Users should upgrade to 6.3.3 or later. Jenkins instances using command-line Git exclusively (the default) are unaffected [4]. No workaround is available for affected versions.

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
org.jenkins-ci.plugins:git-clientMaven
< 6.3.36.3.3

Affected products

2

Patches

1
20090a86c3eb

[SECURITY-3590]

2 files changed · +211 9
  • src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java+67 9 modified
    @@ -125,7 +125,6 @@
     import org.eclipse.jgit.submodule.SubmoduleWalk;
     import org.eclipse.jgit.transport.CredentialsProvider;
     import org.eclipse.jgit.transport.FetchConnection;
    -import org.eclipse.jgit.transport.HttpTransport;
     import org.eclipse.jgit.transport.PushResult;
     import org.eclipse.jgit.transport.RefSpec;
     import org.eclipse.jgit.transport.RemoteConfig;
    @@ -739,6 +738,24 @@ private void setTransportTimeout(TransportCommand transport, String operationNam
             listener.getLogger().println(" > JGit " + operationName + TIMEOUT_LOG_PREFIX + timeoutInMinutes);
         }
     
    +    /**
    +     * Returns true if protocol in url is not supported by Jenkins.
    +     * @return true if protocol in url is not supported by Jenkins.
    +     */
    +    private boolean unsupportedProtocol(String url) {
    +        // SECURITY-3950 reported that an attacker could enumerate the
    +        // controller file system with the Amazon S3 protocol in JGit.
    +        // Command line git does not support amazon-s3 protocol.
    +        // Command line git is the reference implementation for the plugin.
    +        // Do not call JGit with amazon-s3 protocol URL.
    +        // Do not report null URL as unsupported since null was allowed previously
    +        return url != null && url.startsWith("amazon-s3:");
    +    }
    +
    +    private boolean unsupportedProtocol(URIish url) {
    +        return url != null && unsupportedProtocol(url.toString());
    +    }
    +
         /**
          * fetch_.
          *
    @@ -825,6 +842,9 @@ public void execute() throws GitException {
                         if (url == null) {
                             throw new GitException("FetchCommand requires a valid repository url in remote config");
                         }
    +                    if (unsupportedProtocol(url)) {
    +                        throw new GitException("unsupported protocol in URL " + url);
    +                    }
                         fetch.setRemote(url.toString());
                         fetch.setCredentialsProvider(getProvider());
                         fetch.setTransportConfigCallback(getTransportConfigCallback());
    @@ -992,27 +1012,33 @@ private TransportConfigCallback getTransportConfigCallback() {
             return transport -> {
                 if (transport instanceof SshTransport sshTransport) {
                     sshTransport.setSshSessionFactory(buildSshdSessionFactory(this.hostKeyVerifierFactory));
    -            }
    -            if (transport instanceof HttpTransport) {
    -                ((TransportHttp) transport)
    -                        .setHttpConnectionFactory(new PreemptiveAuthHttpClientConnectionFactory(getProvider()));
    +            } else if (transport instanceof TransportHttp transportHttp) {
    +                transportHttp.setHttpConnectionFactory(new PreemptiveAuthHttpClientConnectionFactory(getProvider()));
    +            } else if (transport instanceof org.eclipse.jgit.transport.TransportAmazonS3) {
    +                // SECURITY-3590 safety measure - see unsupportedProtocol() for details
    +                throw new GitException("Unsupported protocol in URL");
                 }
             };
         }
     
         private void decorateTransport(Transport tn) {
             if (tn instanceof SshTransport transport) {
                 transport.setSshSessionFactory(buildSshdSessionFactory(getHostKeyFactory()));
    -        }
    -        if (tn instanceof HttpTransport) {
    -            ((TransportHttp) tn).setHttpConnectionFactory(new PreemptiveAuthHttpClientConnectionFactory(getProvider()));
    +        } else if (tn instanceof TransportHttp transportHttp) {
    +            transportHttp.setHttpConnectionFactory(new PreemptiveAuthHttpClientConnectionFactory(getProvider()));
    +        } else if (tn instanceof org.eclipse.jgit.transport.TransportAmazonS3) {
    +            // SECURITY-3590 safety measure - see unsupportedProtocol() for details
    +            throw new GitException("Unsupported protocol in URL");
             }
         }
     
         /** {@inheritDoc} */
         @Override
         public Map<String, ObjectId> getRemoteReferences(String url, String pattern, boolean headsOnly, boolean tagsOnly)
                 throws GitException, InterruptedException {
    +        if (unsupportedProtocol(url)) {
    +            throw new GitException("unsupported protocol in URL " + url);
    +        }
             Map<String, ObjectId> references = new HashMap<>();
             String regexPattern = null;
             if (pattern != null) {
    @@ -1054,6 +1080,9 @@ public Map<String, ObjectId> getRemoteReferences(String url, String pattern, boo
         public Map<String, String> getRemoteSymbolicReferences(String url, String pattern)
                 throws GitException, InterruptedException {
             Map<String, String> references = new HashMap<>();
    +        if (unsupportedProtocol(url)) {
    +            throw new GitException("unsupported protocol in URL " + url);
    +        }
             String regexPattern = null;
             if (pattern != null) {
                 regexPattern = replaceGlobCharsWithRegExChars(pattern);
    @@ -1126,6 +1155,9 @@ private String replaceGlobCharsWithRegExChars(String glob) {
         /** {@inheritDoc} */
         @Override
         public ObjectId getHeadRev(String remoteRepoUrl, String branchSpec) throws GitException {
    +        if (unsupportedProtocol(remoteRepoUrl)) {
    +            throw new GitException("unsupported protocol in URL " + remoteRepoUrl);
    +        }
             try (Repository repo = openDummyRepository();
                     final Transport tn = Transport.open(repo, new URIish(remoteRepoUrl))) {
                 final String branchName = extractBranchNameFromBranchSpec(branchSpec);
    @@ -1198,6 +1230,9 @@ public FilePath getWorkTree() {
         /** {@inheritDoc} */
         @Override
         public void setRemoteUrl(String name, String url) throws GitException {
    +        if (unsupportedProtocol(url)) {
    +            throw new GitException("unsupported protocol in URL " + url);
    +        }
             try (Repository repo = getRepository()) {
                 StoredConfig config = repo.getConfig();
                 config.setString("remote", name, "url", url);
    @@ -1212,6 +1247,9 @@ public void setRemoteUrl(String name, String url) throws GitException {
         /** {@inheritDoc} */
         @Override
         public void addRemoteUrl(String name, String url) throws GitException {
    +        if (unsupportedProtocol(url)) {
    +            throw new GitException("unsupported protocol in URL " + url);
    +        }
             try (Repository repo = getRepository()) {
                 StoredConfig config = repo.getConfig();
     
    @@ -1670,6 +1708,9 @@ private RepositoryBuilder newRepositoryBuilder() {
                 @Override
                 public void execute() throws GitException {
                     try {
    +                    if (unsupportedProtocol(url)) {
    +                        throw new GitException("unsupported protocol in URL " + url);
    +                    }
                         // the directory needs to be clean or else JGit complains
                         if (workspace.exists()) {
                             Util.deleteContentsRecursive(workspace);
    @@ -2006,6 +2047,9 @@ public List<IndexEntry> getSubmodules(String treeIsh) throws GitException {
         /** {@inheritDoc} */
         @Override
         public void addSubmodule(String remoteURL, String subdir) throws GitException {
    +        if (unsupportedProtocol(remoteURL)) {
    +            throw new GitException("unsupported protocol in URL " + remoteURL);
    +        }
             try (Repository repo = getRepository()) {
                 git(repo).submoduleAdd().setPath(subdir).setURI(remoteURL).call();
             } catch (GitAPIException e) {
    @@ -2180,7 +2224,11 @@ private Set<String> listRemoteBranches(String remote)
             Set<String> branches = new HashSet<>();
             try (final Repository repo = getRepository()) {
                 StoredConfig config = repo.getConfig();
    -            try (final Transport tn = Transport.open(repo, new URIish(config.getString("remote", remote, "url")))) {
    +            String remoteUrl = config.getString("remote", remote, "url");
    +            if (unsupportedProtocol(remoteUrl)) {
    +                throw new GitException("unsupported protocol in URL " + remoteUrl);
    +            }
    +            try (final Transport tn = Transport.open(repo, new URIish(remoteUrl))) {
                     decorateTransport(tn);
                     tn.setCredentialsProvider(getProvider());
                     try (final FetchConnection c = tn.openFetch()) {
    @@ -2250,6 +2298,9 @@ public PushCommand timeout(Integer timeout) {
     
                 @Override
                 public void execute() throws GitException {
    +                if (unsupportedProtocol(remote)) {
    +                    throw new GitException("unsupported protocol in URL " + remote);
    +                }
                     try (Repository repo = getRepository()) {
                         RefSpec ref =
                                 (refspec != null) ? new RefSpec(fixRefSpec(refspec, repo)) : Transport.REFSPEC_PUSH_ALL;
    @@ -2970,6 +3021,9 @@ public String getSubmoduleUrl(String name) throws GitException, InterruptedExcep
         @Deprecated
         @Override
         public void setSubmoduleUrl(String name, String url) throws GitException {
    +        if (unsupportedProtocol(url)) {
    +            throw new GitException("unsupported protocol in URL " + url);
    +        }
             try (Repository repo = getRepository()) {
                 StoredConfig config = repo.getConfig();
                 config.setString("submodule", name, "url", url);
    @@ -3261,7 +3315,11 @@ public String getDefaultRemote(String _default_) throws GitException, Interrupte
         /** {@inheritDoc} */
         @Deprecated
         @Override
    +    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "JGit interaction with spotbugs")
         public void setRemoteUrl(String name, String url, String GIT_DIR) throws GitException, InterruptedException {
    +        if (unsupportedProtocol(url)) {
    +            throw new GitException("unsupported protocol in URL " + url);
    +        }
             try (Repository repo =
                     new RepositoryBuilder().setGitDir(new File(GIT_DIR)).build()) {
                 StoredConfig config = repo.getConfig();
    
  • src/test/java/org/jenkinsci/plugins/gitclient/JGitAPIImplUnsupportedProtocolTest.java+144 0 added
    @@ -0,0 +1,144 @@
    +package org.jenkinsci.plugins.gitclient;
    +
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.is;
    +import static org.junit.Assert.assertThrows;
    +
    +import hudson.model.TaskListener;
    +import hudson.plugins.git.GitException;
    +import java.util.List;
    +import java.util.Random;
    +import org.eclipse.jgit.lib.Config;
    +import org.eclipse.jgit.transport.RefSpec;
    +import org.eclipse.jgit.transport.RemoteConfig;
    +import org.eclipse.jgit.transport.URIish;
    +import org.junit.Before;
    +import org.junit.Rule;
    +import org.junit.Test;
    +import org.junit.rules.TemporaryFolder;
    +
    +/**
    + * JGit supports the 'amazon-s3' protocol but the Jenkins git client
    + * plugin is not tested with that protocol.  Command line git does not
    + * support the 'amazon-s3' protocol.  Since command line git is the
    + * reference implementation for the git client plugin, there is no
    + * reason to support the 'amazon-s3' protocol.  Plugin releases 6.2.0
    + * and before would report a class cast exception if a user used the
    + * 'amazon-s3' protocol.  It was not usable by users.
    + */
    +public class JGitAPIImplUnsupportedProtocolTest {
    +
    +    private JGitAPIImpl jgit = null;
    +
    +    private URIish url = null;
    +    private String urlStr = null;
    +
    +    private String expectedMessage = null;
    +
    +    private final Random random = new Random();
    +
    +    @Rule
    +    public TemporaryFolder folder = new TemporaryFolder();
    +
    +    @Before
    +    public void setup() throws Exception {
    +        url = new URIish("amazon-s3://@host/path-" + random.nextInt());
    +        urlStr = url.toString();
    +        jgit = new JGitAPIImpl(folder.getRoot(), TaskListener.NULL);
    +        expectedMessage = "unsupported protocol in URL " + urlStr;
    +    }
    +
    +    @Test
    +    public void testFetchCommand() throws Exception {
    +        List<RefSpec> refspecs = null;
    +        var thrown = assertThrows(
    +                GitException.class, () -> jgit.fetch_().from(url, refspecs).execute());
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testGetRemoteReferences() throws Exception {
    +        String pattern = ".*";
    +        boolean headsOnly = random.nextBoolean();
    +        boolean tagsOnly = random.nextBoolean();
    +        var thrown =
    +                assertThrows(GitException.class, () -> jgit.getRemoteReferences(urlStr, pattern, headsOnly, tagsOnly));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testGetRemoteSymbolicReferences() throws Exception {
    +        String pattern = ".*";
    +        var thrown = assertThrows(GitException.class, () -> jgit.getRemoteSymbolicReferences(urlStr, pattern));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testGetHeadRev() throws Exception {
    +        String branchSpec = "origin/my-branch-" + random.nextInt();
    +        var thrown = assertThrows(GitException.class, () -> jgit.getHeadRev(urlStr, branchSpec));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testSetRemoteUrl() throws Exception {
    +        String name = "upstream-" + random.nextInt();
    +        var thrown = assertThrows(GitException.class, () -> jgit.setRemoteUrl(name, urlStr));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testAddRemoteUrl() throws Exception {
    +        String name = "upstream-" + random.nextInt();
    +        var thrown = assertThrows(GitException.class, () -> jgit.addRemoteUrl(name, urlStr));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testCloneCommand() throws Exception {
    +        var thrown =
    +                assertThrows(GitException.class, () -> jgit.clone_().url(urlStr).execute());
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testAddSubmodule() throws Exception {
    +        var thrown = assertThrows(GitException.class, () -> jgit.addSubmodule(urlStr, "subdir"));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testPushCommand() throws Exception {
    +        var thrown = assertThrows(GitException.class, () -> jgit.push().to(url).execute());
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    public void testPrune() throws Exception {
    +        // Create a local git repository
    +        jgit.init_().workspace(folder.getRoot().getAbsolutePath()).execute();
    +        // Locally modify the remote URL inside existing local git repository
    +        String remoteName = "amazons3-remote";
    +        jgit.config(GitClient.ConfigLevel.LOCAL, "remote." + remoteName + ".url", urlStr);
    +        // Confirm that locally modified remote URL with amazon-s3 protocol throws
    +        var thrown = assertThrows(GitException.class, () -> jgit.prune(new RemoteConfig(new Config(), remoteName)));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    @Deprecated
    +    public void testSetSubmoduleUrl() throws Exception {
    +        var thrown = assertThrows(GitException.class, () -> jgit.setSubmoduleUrl("submodule-name", urlStr));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +
    +    @Test
    +    @Deprecated
    +    public void testSetRemoteUrl3Args() throws Exception {
    +        // Create a local git repository so that remote URL is not altered in working directory
    +        String repoDir = folder.getRoot().getAbsolutePath();
    +        jgit.init_().workspace(repoDir).execute();
    +        var thrown = assertThrows(GitException.class, () -> jgit.setRemoteUrl("remote-name", urlStr, repoDir));
    +        assertThat(thrown.getMessage(), is(expectedMessage));
    +    }
    +}
    

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