show the resource an auth47 challenge is signed for
What changed, and why it matters
This commit improves the wallet's login confirmation message when using the Auth47 authentication protocol. Previously, the wallet only told the user which website would receive the login response (the callback). Now it also shows the 'resource' the challenge is actually signed for, and warns if the resource differs from the callback. This helps prevent a malicious or misconfigured service from tricking a user into signing a login challenge for one site while sending the response to another.
Treat this as a security-hardening improvement rather than an active vulnerability. Review whether the resource URI parsing (new URI(resource).getHost()) can throw or be confused by malformed input, and ensure the dialog text cannot be used to mislead users via a crafted resource string. Consider adding tests for resource/callback mismatch cases.
Security signals we found
UI now distinguishes the signed resource from the response callback
New warning path for resource/callback host mismatch
New handling for unrecognised resource URIs
Auth47 challenge scoping comment indicates prior ambiguity was a security concern
Evidence from the diff
Auth47 parses a BIP47-style authentication URI containing a nonce (host), callback URL, optional expiry, and optional resource (‘r’). Before this patch, the wallet selection dialog used ‘login to
Changed components
src/main/java/com/sparrowwallet/sparrow/AppServices.javasrc/main/java/com/sparrowwallet/sparrow/net/Auth47.javaInspect captured patch +27 / −2
### src/main/java/com/sparrowwallet/sparrow/AppServices.java
@@ -1102,7 +1102,7 @@ private static void openAuth47Uri(URI uri) {
try {
Auth47 auth47 = new Auth47(uri);
List<ScriptType> scriptTypes = PaymentCode.SEGWIT_SCRIPT_TYPES;
- Wallet wallet = selectWallet(List.of(PolicyType.SINGLE_HD), scriptTypes, false, true, "login to " + auth47.getCallback().getHost(), true);
+ Wallet wallet = selectWallet(List.of(PolicyType.SINGLE_HD), scriptTypes, false, true, auth47.getLoginMessage(), true);
if(wallet != null) {
try {
### src/main/java/com/sparrowwallet/sparrow/net/Auth47.java
@@ -36,6 +36,7 @@ public class Auth47 {
private boolean srbn;
private String srbnName;
private String resource;
+ private boolean resourceSpecified;
public Auth47(URI uri) throws MalformedURLException, URISyntaxException {
this.nonce = uri.getHost();
@@ -79,8 +80,11 @@ public Auth47(URI uri) throws MalformedURLException, URISyntaxException {
this.expiry = parameterMap.get("e");
this.resource = parameterMap.get("r");
+ String defaultResource = srbn ? "srbn" : strCallback;
if(resource == null) {
- this.resource = srbn ? "srbn" : strCallback;
+ this.resource = defaultResource;
+ } else {
+ this.resourceSpecified = !resource.equals(defaultResource);
}
}
@@ -187,6 +191,27 @@ public URL getCallback() {
return callback;
}
+ //The signed challenge is scoped to the resource, which need not be the callback the response is sent to
+ public String getLoginMessage() {
+ String callbackHost = callback.getHost();
+ String resourceHost = resourceSpecified ? getResourceHost() : callbackHost;
+ if(resourceHost == null) {
+ return "login to an unrecognised resource (sending the response to " + callbackHost + ")";
+ } else if(!resourceHost.equalsIgnoreCase(callbackHost)) {
+ return "login to " + resourceHost + " (sending the response to " + callbackHost + ")";
+ }
+
+ return "login to " + callbackHost;
+ }
+
+ private String getResourceHost() {
+ try {
+ return new URI(resource).getHost();
+ } catch(URISyntaxException e) {
+ return null;
+ }
+ }
+
private static class Response {
public Response(String version, String challenge, String signature, String paymentCode, String address) {
this.auth47_response = version;Why this scored 21/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.