Description
Summary
When JGit receives an HTTP 403 (Forbidden) response during git-upload-pack or git-receive-pack, it constructs a generic error message:
git-upload-pack not permitted on https://github.example.com/org/repo.git
The actual reason for the rejection — which the server provides in the HTTP response body — is discarded. In contrast, the native git client reads and displays the response body, providing a much more informative error:
the repository owner has an IP allow list enabled, and <IP> is not permitted to access this repository.
This makes it significantly harder to diagnose access issues when using JGit-based tooling.
Steps to Reproduce
- Configure a GitHub repository (or GitHub Enterprise) with an IP allow list that blocks the client's IP address.
- Attempt to clone or fetch using JGit over HTTPS.
- Observe the error message.
JGit result:
org.eclipse.jgit.errors.TransportException: https://github.example.com/org/repo.git: git-upload-pack not permitted on https://github.example.com/org/repo.git/
Native git result:
fatal: unable to access 'https://github.example.com/org/repo.git/': The requested URL returned error: 403
the repository owner has an IP allow list enabled, and <IP> is not permitted to access this repository.
Expected Behavior
JGit should read the HTTP error response body on 403 responses and include it in the TransportException message, e.g.:
git-upload-pack not permitted on https://github.example.com/org/repo.git/: the repository owner has an IP allow list enabled, and <IP> is not permitted to access this repository.
Root Cause Analysis
In TransportHttp.java, both HTTP_FORBIDDEN handlers (in connect() at ~line 701 and in Service.sendRequest() at ~line 1710) construct the error message using only the URL and service name:
case HttpConnection.HTTP_FORBIDDEN:
throw new TransportException(uri, MessageFormat.format(
JGitText.get().serviceNotPermitted, baseUrl, service));
The HTTP response body (error stream) is never read. The HttpConnection interface also does not expose a getErrorStream() method, so even if the transport wanted to read it, there is no API to do so.
Proposed Fix
Three changes are needed:
1. Add getErrorStream() to HttpConnection interface
File: org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
Add a default method (to maintain backward compatibility with custom implementations):
/**
* Returns an error stream for reading the HTTP error response body
* on error status codes (4xx, 5xx).
*
* @return the error stream, or {@code null} if not available
* @see java.net.HttpURLConnection#getErrorStream()
* @since 7.1
*/
default InputStream getErrorStream() {
return null;
}
2. Implement getErrorStream() in JDKHttpConnection
File: org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
@Override
public InputStream getErrorStream() {
return wrappedUrlConnection.getErrorStream();
}
3. Read error body in TransportHttp on 403 responses
File: org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
Add a helper method:
private static String readErrorBody(HttpConnection conn) {
try {
InputStream errorStream = conn.getErrorStream();
if (errorStream == null) {
return null;
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(errorStream, UTF_8))) {
StringBuilder body = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (body.length() > 0) {
body.append(' ');
}
body.append(line);
if (body.length() > 4096) {
break; // Limit how much we read
}
}
String result = body.toString().trim();
// If response is HTML, strip tags for readability
if (result.contains("<") && result.contains(">")) {
result = result.replaceAll("<[^>]+>", " ")
.replaceAll("\\s+", " ")
.trim();
}
return result.isEmpty() ? null : result;
}
} catch (IOException e) {
return null;
}
}
Then update both HTTP_FORBIDDEN cases to:
case HttpConnection.HTTP_FORBIDDEN:
String forbiddenMsg = MessageFormat.format(
JGitText.get().serviceNotPermitted, baseUrl, service);
String errorBody = readErrorBody(conn);
if (errorBody != null && !errorBody.isEmpty()) {
forbiddenMsg += ": " + errorBody;
}
throw new TransportException(uri, forbiddenMsg);
Motivation
This issue was seen when an enterprise organisation decides to add IP allow list for that specific organisation. And when a clone was done it was not clear what the exact issue was since we thought it was due to token not having enough permission.
Alternatives considered
No response
Additional context
No response
Description
Summary
When JGit receives an HTTP 403 (Forbidden) response during
git-upload-packorgit-receive-pack, it constructs a generic error message:The actual reason for the rejection — which the server provides in the HTTP response body — is discarded. In contrast, the native
gitclient reads and displays the response body, providing a much more informative error:This makes it significantly harder to diagnose access issues when using JGit-based tooling.
Steps to Reproduce
JGit result:
Native git result:
Expected Behavior
JGit should read the HTTP error response body on 403 responses and include it in the
TransportExceptionmessage, e.g.:Root Cause Analysis
In
TransportHttp.java, bothHTTP_FORBIDDENhandlers (inconnect()at ~line 701 and inService.sendRequest()at ~line 1710) construct the error message using only the URL and service name:The HTTP response body (error stream) is never read. The
HttpConnectioninterface also does not expose agetErrorStream()method, so even if the transport wanted to read it, there is no API to do so.Proposed Fix
Three changes are needed:
1. Add
getErrorStream()toHttpConnectioninterfaceFile:
org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.javaAdd a default method (to maintain backward compatibility with custom implementations):
2. Implement
getErrorStream()inJDKHttpConnectionFile:
org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java3. Read error body in
TransportHttpon 403 responsesFile:
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.javaAdd a helper method:
Then update both
HTTP_FORBIDDENcases to:Motivation
This issue was seen when an enterprise organisation decides to add IP allow list for that specific organisation. And when a clone was done it was not clear what the exact issue was since we thought it was due to token not having enough permission.
Alternatives considered
No response
Additional context
No response