use owner only temp directory for wallet import and export
What changed, and why it matters
This commit changes how Sparrow Wallet handles temporary files when importing or exporting wallets. Previously, temporary wallet files were created in the system's shared temporary directory, where other users or processes on the same computer could potentially read or tamper with them. The new code creates a private, owner-only temporary directory and securely deletes its contents afterward. This reduces the risk that sensitive wallet data briefly exposed during import/export could be accessed by others on the same machine.
Users running multi-user systems or shared environments should upgrade to the version containing this commit. Developers should verify that `IOUtils.secureDelete()` overwrites file contents and that the temporary directory permissions are restrictive on all supported platforms. Consider auditing other file I/O paths for similar shared-temp-directory usage.
Security signals we found
Creation of owner-only temporary directory for sensitive wallet import/export operations
Replacement of shared system temp file creation with isolated temp directory
Use of secureDelete for wiping temporary wallet files
Addition of closeAndWait to ensure persistence resources are released before cleanup
Potential mitigation of local privilege-escalation/information-disclosure via temp directory race conditions
Evidence from the diff
The patch replaces File.createTempFile() calls in Sparrow.java with Files.createTempDirectory("sparrow"), placing temporary wallet files inside a dedicated directory during import and export. It also introduces deleteTempDirectory() which uses IOUtils.secureDelete() for file wiping and deletes the directory. Storage.java adds closeAndWait() to ensure the persistence layer is fully closed before the temporary directory is removed. The change mitigates local information-disclosure and tampering risks from world-readable/shared temporary directories, though the commit message and diff alone do not confirm a specific vulnerability report or CVE.
Changed components
src/main/java/com/sparrowwallet/sparrow/io/Sparrow.javasrc/main/java/com/sparrowwallet/sparrow/io/Storage.javaInspect captured patch +30 / −18
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java b/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java
index dcd1c86..823a6b6 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java
@@ -1,6 +1,6 @@
package com.sparrowwallet.sparrow.io;
-import com.google.common.io.Files;
+import com.sparrowwallet.drongo.IOUtils;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletModel;
import com.sparrowwallet.sparrow.AppServices;
@@ -11,6 +11,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Map;
@@ -29,13 +30,14 @@ public class Sparrow implements WalletImport, WalletExport {
@Override
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
+ File tempDir = null;
try {
Wallet exportedWallet = !wallet.isMasterWallet() ? wallet.getMasterWallet() : wallet;
PersistenceType persistenceType = PersistenceType.DB;
Persistence persistence = persistenceType.getInstance();
Storage storage = AppServices.get().getOpenWallets().get(exportedWallet);
- File tempFile = File.createTempFile(exportedWallet.getName() + "tmp", "." + persistenceType.getExtension());
- tempFile.delete();
+ tempDir = Files.createTempDirectory("sparrow").toFile();
+ File tempFile = new File(tempDir, exportedWallet.getName() + "." + persistenceType.getExtension());
Storage tempStorage = new Storage(persistence, tempFile);
tempStorage.setKeyDeriver(storage.getKeyDeriver());
tempStorage.setEncryptionPubKey(storage.getEncryptionPubKey());
@@ -46,12 +48,13 @@ public class Sparrow implements WalletImport, WalletExport {
tempStorage.saveWallet(childWallet);
}
persistence.close();
- Files.copy(tempStorage.getWalletFile(), outputStream);
+ Files.copy(tempStorage.getWalletFile().toPath(), outputStream);
outputStream.flush();
- tempStorage.getWalletFile().delete();
} catch(Exception e) {
log.error("Error exporting Sparrow wallet file", e);
throw new ExportException("Error exporting Sparrow wallet file", e);
+ } finally {
+ deleteTempDirectory(tempDir);
}
}
@@ -88,11 +91,11 @@ public class Sparrow implements WalletImport, WalletExport {
@Override
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
Storage storage = null;
- Wallet wallet = null;
- File tempFile = null;
+ File tempDir = null;
try {
- tempFile = File.createTempFile("sparrow", null);
- java.nio.file.Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ tempDir = Files.createTempDirectory("sparrow").toFile();
+ File tempFile = new File(tempDir, "sparrow");
+ Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
PersistenceType persistenceType = Storage.detectPersistenceType(tempFile);
persistenceType = (persistenceType == null ? PersistenceType.JSON : persistenceType);
if(persistenceType != PersistenceType.JSON || !isEncrypted(tempFile)) {
@@ -102,6 +105,7 @@ public class Sparrow implements WalletImport, WalletExport {
}
storage = new Storage(persistenceType, tempFile);
+ Wallet wallet;
if(!isEncrypted(tempFile)) {
wallet = storage.loadUnencryptedWallet().getWallet();
} else {
@@ -118,19 +122,23 @@ public class Sparrow implements WalletImport, WalletExport {
throw new ImportException("Error importing Sparrow wallet", e);
} finally {
if(storage != null) {
- storage.close();
+ storage.closeAndWait();
}
- if(tempFile != null) {
- if(wallet != null) {
- File migratedWalletFile = Storage.getExistingWallet(tempFile.getParentFile(), wallet.getName());
- if(migratedWalletFile != null) {
- migratedWalletFile.delete();
- }
- }
+ deleteTempDirectory(tempDir);
+ }
+ }
- tempFile.delete();
+ private void deleteTempDirectory(File tempDir) {
+ if(tempDir != null) {
+ File[] tempFiles = tempDir.listFiles();
+ if(tempFiles != null) {
+ for(File file : tempFiles) {
+ IOUtils.secureDelete(file);
+ }
}
+
+ tempDir.delete();
}
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
index 1945b08..075c6a9 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
@@ -140,6 +140,10 @@ public class Storage {
closePersistenceService.start();
}
+ public void closeAndWait() {
+ persistence.close();
+ }
+
public void restorePublicKeysFromSeed(Wallet wallet, Key key) throws MnemonicException {
checkWalletNetwork(wallet);
Why this scored 58/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.