What changed, and why it matters
This commit tightens what characters are allowed in Sparrow wallet file names. It appears to block a way that a specially crafted wallet file name could inject extra commands into the H2 database connection string (JDBC URL), which might let an attacker who can get a user to open a maliciously named wallet file perform unexpected database operations. The patch is defensive and partial: it adds a blocklist for semicolon-prefixed parameters in the file name and also removes the semicolon from allowed characters when naming wallets.
Treat this as a security hardening fix. Users should upgrade to a version containing this commit and avoid opening wallet files from untrusted sources. Developers should consider a more robust allowlist for wallet file names and use a safer method of constructing the H2 JDBC URL (e.g., passing options via the H2 API rather than string concatenation).
Security signals we found
JDBC URL injection / connection-string injection via file name
H2 database URL parameter injection
Input validation added for wallet file names
Semicolon added to disallowed filename characters in UI
StorageException thrown on suspicious file names
Evidence from the diff
Sparrow uses H2 as its wallet storage backend and builds a JDBC URL from the wallet file’s absolute path. H2 JDBC URLs accept semicolon-separated parameters (e.g., ;INIT=…, ;CIPHER=AES). If an attacker can control the wallet file name and embed ;KEY=VALUE patterns, the resulting URL could carry unintended H2 settings or commands. The patch adds a regex check ;\w+= in DbPersistence.getUrl() that throws StorageException if the file name contains such a pattern, and updates WalletNameDialog to replace semicolons with underscores when a wallet is named. This is a blacklist-style fix and does not comprehensively validate or encode the file path in the JDBC URL.
Changed components
com.sparrowwallet.sparrow.io.db.DbPersistencecom.sparrowwallet.sparrow.control.WalletNameDialogInspect captured patch +7 / −2
diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java
index c0d5a4f..7971771 100644
--- a/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java
+++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java
@@ -73,7 +73,7 @@ public class WalletNameDialog extends Dialog<WalletNameDialog.NameAndBirthDate>
name = (CustomTextField)TextFields.createClearableTextField();
name.setText(initialName);
name.setTextFormatter(new TextFormatter<>((change) -> {
- change.setText(change.getText().replaceAll("[\\\\/:*?\"<>|]", "_"));
+ change.setText(change.getText().replaceAll("[\\\\/:*?\"<>|;]", "_"));
return change;
}));
content.getChildren().add(name);
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java
index 5802809..d93494c 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java
@@ -36,6 +36,7 @@ import java.nio.file.StandardCopyOption;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.ExecutorService;
+import java.util.regex.Pattern;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -54,6 +55,7 @@ public class DbPersistence implements Persistence {
private static final String H2_USER = "sa";
private static final String H2_PASSWORD = "";
public static final String MIGRATION_RESOURCES_DIR = "com/sparrowwallet/sparrow/sql/";
+ private static final Pattern JDBC_URL_INJECTION_PATTERN = Pattern.compile(";\\w+=");
private HikariDataSource dataSource;
private AsymmetricKeyDeriver keyDeriver;
@@ -695,7 +697,10 @@ public class DbPersistence implements Persistence {
}
}
- private String getUrl(File walletFile, String password) {
+ private String getUrl(File walletFile, String password) throws StorageException {
+ if(JDBC_URL_INJECTION_PATTERN.matcher(walletFile.getName()).find()) {
+ throw new StorageException("Wallet file name contains invalid characters");
+ }
return "jdbc:h2:" + walletFile.getAbsolutePath().replace("." + getType().getExtension(), "") + ";INIT=SET TRACE_LEVEL_FILE=4;TRACE_LEVEL_FILE=4;DEFRAG_ALWAYS=true;MAX_COMPACT_TIME=5000;DATABASE_TO_UPPER=false" + (password == null ? "" : ";CIPHER=AES");
}
Why this scored 56/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.