fix potential date formatting concurrency issue on backup
What changed, and why it matters
This commit fixes a rare threading bug in how Sparrow Wallet names its automatic wallet backups. The old code used a date formatter that is not safe when multiple threads use it at the same time, which could in theory produce a malformed backup filename or throw an exception. The new code uses a modern, thread-safe date formatter. There is no direct evidence this is exploitable by an attacker; it is best described as a robustness fix.
Treat as a low-risk maintenance/robustness fix. No urgent security response is warranted based on the commit alone. If maintaining a fork, merge the change to avoid rare backup failures under concurrency.
Security signals we found
Thread-safety fix for shared date formatter
Use of non-thread-safe SimpleDateFormat in backup path replaced
No input from untrusted sources is parsed or formatted
No memory-safety, cryptographic, or authorization boundary crossed
Evidence from the diff
Storage.java replaced the shared java.text.SimpleDateFormat instance BACKUP_DATE_FORMAT with java.time.DateTimeFormatter. SimpleDateFormat is not thread-safe; concurrent calls to format/parse can corrupt output or throw exceptions. The backupWallet() and hasStartedSince() methods were updated to use LocalDateTime and DateTimeFormatter. The change is a defensive concurrency fix. No verified references describe a security incident, CVE, or researcher attribution.
Changed components
src/main/java/com/sparrowwallet/sparrow/io/Storage.javaWallet backup filename generationWallet backup date parsingInspect captured patch +7 / −7
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
index 075c6a9..4856ef4 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
@@ -25,8 +25,9 @@ import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -37,7 +38,7 @@ public class Storage {
private static final Logger log = LoggerFactory.getLogger(Storage.class);
public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECKey.fromPrivate(Utils.hexToBytes("885e5a09708a167ea356a252387aa7c4893d138d632e296df8fbf5c12798bd28")));
- private static final DateFormat BACKUP_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
+ private static final DateTimeFormatter BACKUP_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final Pattern DATE_PATTERN = Pattern.compile(".+-([0-9]{14}?).*");
public static final String SPARROW_DIR = ".sparrow";
@@ -238,9 +239,8 @@ public class Storage {
private void backupWallet(String prefix) throws IOException {
File backupDir = getWalletsBackupDir();
- Date backupDate = new Date();
String walletName = persistence.getWalletName(walletFile, null);
- String dateSuffix = "-" + BACKUP_DATE_FORMAT.format(backupDate);
+ String dateSuffix = "-" + BACKUP_DATE_FORMAT.format(LocalDateTime.now());
String backupName = walletName + dateSuffix + walletFile.getName().substring(walletName.length());
if(prefix != null) {
@@ -275,9 +275,9 @@ public class Storage {
private boolean hasStartedSince(File lastBackup) {
try {
- Date date = BACKUP_DATE_FORMAT.parse(getBackupDate(lastBackup.getName()));
+ LocalDateTime date = LocalDateTime.parse(getBackupDate(lastBackup.getName()), BACKUP_DATE_FORMAT);
ProcessHandle.Info processInfo = ProcessHandle.current().info();
- return (processInfo.startInstant().isPresent() && processInfo.startInstant().get().isAfter(date.toInstant()));
+ return (processInfo.startInstant().isPresent() && processInfo.startInstant().get().isAfter(date.atZone(ZoneId.systemDefault()).toInstant()));
} catch(Exception e) {
log.error("Error parsing date for backup file " + lastBackup.getName(), e);
return false;
Why this scored 18/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.