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.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.plugins:git-clientMaven | < 6.3.3 | 6.3.3 |
Affected products
2- Range: <=6.3.2 excluding 6.1.4 and 6.2.1
- Jenkins Project/Jenkins Git client Pluginv5Range: 6.3.3
Patches
120090a86c3eb[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- github.com/advisories/GHSA-g2pq-9jr7-w6gvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-58458ghsaADVISORY
- www.jenkins.io/security/advisory/2025-09-03/ghsavendor-advisoryWEB
- www.openwall.com/lists/oss-security/2025/09/03/4ghsaWEB
- github.com/jenkinsci/git-client-plugin/commit/20090a86c3ebc72e5283c882de73e3a4459137bbghsaWEB
News mentions
1- Jenkins Security Advisory 2025-09-03Jenkins Security Advisories · Sep 3, 2025