report bitcoin core rpc errors that carry no result instead of failing on a null response
What changed, and why it matters
This commit fixes how Sparrow Wallet handles error responses from Bitcoin Core's RPC interface. Previously, the app could silently return a null value or crash with a confusing NullPointerException instead of telling the user what actually went wrong. The change makes error messages clearer and adds handling for HTTP 403 'forbidden' responses and non-JSON replies, which can happen when a proxy or TLS issue sits between Sparrow and Bitcoin Core.
Review and merge. The change is a defensive fix that improves failure visibility. Users relying on local Bitcoin Core should verify rpcallowip/rpcwhitelist settings if they encounter the new 403 message. No immediate incident response is indicated by the diff alone.
Security signals we found
Improved error handling for Bitcoin Core RPC failures
HTTP 403 access-denied now surfaced to user
Null result stripping extended from 500 to all 4xx/5xx to preserve JSON-RPC error payloads
Guard added against non-JSON (e.g., proxy/HTML) bodies
Potential crash path (NullPointerException on empty error) removed
Evidence from the diff
BitcoindTransport.pass() now: (1) throws a specific IOException for HTTP 403 from Bitcoin Core; (2) strips the "result":null JSON field for any 4xx/5xx status, not just 500, so JSON-RPC errors are deserialized and thrown instead of being swallowed as a null return; (3) rejects responses that do not start with ‘{’ to avoid a downstream NullPointerException when constructing a JsonRpcException from an empty/HTML body. The fix improves diagnostics but is defensive; it does not by itself prevent a malicious response from being processed.
Changed components
src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindTransport.javaInspect captured patch +11 / −1
### src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindTransport.java
@@ -127,14 +127,18 @@ public String pass(String request) throws IOException {
if(statusCode == 401) {
throw new IOException((cookieFile == null ? "User/pass" : "Cookie file") + " authentication failed");
+ } else if(statusCode == 403) {
+ throw new IOException("Bitcoin Core at " + bitcoindUrl.getAuthority() + " refused RPC access from this computer, check its rpcallowip and rpcwhitelist settings");
}
InputStream inputStream = connection.getErrorStream() == null ? connection.getInputStream() : connection.getErrorStream();
StringBuilder res = new StringBuilder();
try(BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String responseLine;
while((responseLine = br.readLine()) != null) {
- if(statusCode == 500) {
+ //Bitcoin Core returns a null result alongside the error on a failed call, which the client deserializes to a null return value instead of throwing the error.
+ //It answers 400 on an invalid request and 404 on an unknown method, so stripping the null result on 500 alone loses those errors and returns null to the caller.
+ if(statusCode >= 400) {
responseLine = responseLine.replace("\"result\":null,", "");
}
@@ -145,6 +149,12 @@ public String pass(String request) throws IOException {
String response = res.toString();
log.debug("< " + response);
+ //A response carrying neither a result nor an error leaves the client constructing a JsonRpcException from a null error message, which throws a NullPointerException naming nothing.
+ //Bitcoin Core always answers with a JSON-RPC object, so an empty or HTML body here comes from something else on the network path - typically a TLS terminating proxy sent a plain HTTP request.
+ if(!response.startsWith("{")) {
+ throw new IOException("Bitcoin Core at " + bitcoindUrl.getAuthority() + " did not return a JSON-RPC response to the " + bitcoindUrl.getProtocol() + " request (HTTP " + statusCode + ")");
+ }
+
return response;
}
Why this scored 45/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.