prevent opening of already opened xmr/wow/sal wallets
What changed, and why it matters
This commit changes how Stack Wallet initializes Monero-style (XMR, WOW, SAL) wallets. During setup, it now briefly opens the wallet, updates network node settings, immediately closes it, and clears the reference. The stated goal is to avoid leaving wallets open when they are already opened elsewhere. The change is defensive and cleanup-oriented rather than a clear fix for an active attack.
Treat as a hardening/correctness improvement. Review whether the close() call is idempotent and safe when the wallet is already closed, and verify that updateNode() does not depend on the wallet remaining open after init(). Consider adding tests that assert no leaked wallet handles remain after repeated init() calls.
Security signals we found
Resource lifecycle hardening: explicit wallet close after initialization
Prevention of double-open / concurrent wallet handle states
Potential reduction of memory/disk corruption risk from leaked wallet handles
No explicit security claim or CVE reference in commit message
Evidence from the diff
The patch modifies init() paths in LibMoneroWallet, LibWowneroWallet, and LibSalviumWallet. Previously these classes called updateNode() after creating/retrieving a wallet object but did not explicitly close it. Now they assign the wallet to this.wallet, call updateNode(), then call the new cs*.close(wallet, save: true) and set this.wallet = null. The close method is added to the CsMoneroInterface, CsWowneroInterface, and CsSalviumInterface abstractions, plus their generated template implementations, delegating to the underlying lib_monero/lib_wownero/lib_salvium wallet close(). Imports are narrowed with show clauses to reduce cross-interface coupling.
Changed components
lib/wallets/wallet/intermediate/lib_monero_wallet.dartlib/wallets/wallet/intermediate/lib_wownero_wallet.dartlib/wallets/wallet/intermediate/lib_salvium_wallet.dartlib/wl_gen/interfaces/cs_monero_interface.dartlib/wl_gen/interfaces/cs_wownero_interface.dartlib/wl_gen/interfaces/cs_salvium_interface.darttool/wl_templates/XMR_cs_monero_interface_impl.template.darttool/wl_templates/WOW_cs_wownero_interface_impl.template.darttool/wl_templates/SAL_cs_salvium_interface_impl.template.dartInspect captured patch +41 / −7
diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart
index bea4547..b1edd82 100644
--- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart
+++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart
@@ -35,7 +35,8 @@ import '../../../utilities/enums/fee_rate_type_enum.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/stack_file_system.dart';
import '../../../wl_gen/interfaces/cs_monero_interface.dart';
-import '../../../wl_gen/interfaces/cs_salvium_interface.dart';
+import '../../../wl_gen/interfaces/cs_salvium_interface.dart'
+ show WrappedWallet;
import '../../crypto_currency/intermediate/cryptonote_currency.dart';
import '../../isar/models/wallet_info.dart';
import '../../models/tx_data.dart';
@@ -368,10 +369,14 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
key: Wallet.mnemonicPassphraseKey(walletId: walletId),
value: "",
);
+
+ this.wallet = wallet;
+ await updateNode();
+ await csMonero.close(wallet, save: true);
+ this.wallet = null;
} catch (e, s) {
Logging.instance.f("", error: e, stackTrace: s);
}
- await updateNode();
}
return super.init();
diff --git a/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart b/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart
index 020fe1a..cd81916 100644
--- a/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart
+++ b/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart
@@ -32,7 +32,8 @@ import '../../../utilities/amount/amount.dart';
import '../../../utilities/enums/fee_rate_type_enum.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/stack_file_system.dart';
-import '../../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../../wl_gen/interfaces/cs_monero_interface.dart'
+ show CsWalletListener, CsOutput, CsRecipient, CsPendingTransaction;
import '../../../wl_gen/interfaces/cs_salvium_interface.dart';
import '../../crypto_currency/intermediate/cryptonote_currency.dart';
import '../../isar/models/wallet_info.dart';
@@ -349,10 +350,14 @@ abstract class LibSalviumWallet<T extends CryptonoteCurrency>
key: Wallet.mnemonicPassphraseKey(walletId: walletId),
value: "",
);
+
+ this.wallet = wallet;
+ await updateNode();
+ await csSalvium.close(wallet, save: true);
+ this.wallet = null;
} catch (e, s) {
Logging.instance.f("", error: e, stackTrace: s);
}
- await updateNode();
}
return super.init();
diff --git a/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart
index b6ad2ef..0dcdaec 100644
--- a/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart
+++ b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart
@@ -34,8 +34,10 @@ import '../../../utilities/amount/amount.dart';
import '../../../utilities/enums/fee_rate_type_enum.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/stack_file_system.dart';
-import '../../../wl_gen/interfaces/cs_monero_interface.dart';
-import '../../../wl_gen/interfaces/cs_salvium_interface.dart';
+import '../../../wl_gen/interfaces/cs_monero_interface.dart'
+ show CsWalletListener, CsOutput, CsRecipient, CsPendingTransaction;
+import '../../../wl_gen/interfaces/cs_salvium_interface.dart'
+ show WrappedWallet;
import '../../../wl_gen/interfaces/cs_wownero_interface.dart';
import '../../crypto_currency/intermediate/cryptonote_currency.dart';
import '../../isar/models/wallet_info.dart';
@@ -372,10 +374,14 @@ abstract class LibWowneroWallet<T extends CryptonoteCurrency>
key: Wallet.mnemonicPassphraseKey(walletId: walletId),
value: "",
);
+
+ this.wallet = wallet;
+ await updateNode();
+ await csWownero.close(wallet, save: true);
+ this.wallet = null;
} catch (e, s) {
Logging.instance.f("", error: e, stackTrace: s);
}
- await updateNode();
}
return super.init();
diff --git a/lib/wl_gen/interfaces/cs_monero_interface.dart b/lib/wl_gen/interfaces/cs_monero_interface.dart
index 898a4a4..4541c95 100644
--- a/lib/wl_gen/interfaces/cs_monero_interface.dart
+++ b/lib/wl_gen/interfaces/cs_monero_interface.dart
@@ -155,6 +155,8 @@ abstract class CsMoneroInterface {
bool validateAddress(String address, int network);
String getSeed(WrappedWallet wallet);
+
+ Future<void> close(WrappedWallet wallet, {bool save = false});
}
// forwarding class
diff --git a/lib/wl_gen/interfaces/cs_salvium_interface.dart b/lib/wl_gen/interfaces/cs_salvium_interface.dart
index 53ad91b..58be07e 100644
--- a/lib/wl_gen/interfaces/cs_salvium_interface.dart
+++ b/lib/wl_gen/interfaces/cs_salvium_interface.dart
@@ -169,6 +169,8 @@ abstract class CsSalviumInterface {
bool validateAddress(String address, int network);
String getSeed(WrappedWallet wallet);
+
+ Future<void> close(WrappedWallet wallet, {bool save = false});
}
// lol...
diff --git a/lib/wl_gen/interfaces/cs_wownero_interface.dart b/lib/wl_gen/interfaces/cs_wownero_interface.dart
index 2e317d9..50a1522 100644
--- a/lib/wl_gen/interfaces/cs_wownero_interface.dart
+++ b/lib/wl_gen/interfaces/cs_wownero_interface.dart
@@ -156,4 +156,6 @@ abstract class CsWowneroInterface {
bool validateAddress(String address, int network);
String getSeed(WrappedWallet wallet);
+
+ Future<void> close(WrappedWallet wallet, {bool save = false});
}
diff --git a/tool/wl_templates/SAL_cs_salvium_interface_impl.template.dart b/tool/wl_templates/SAL_cs_salvium_interface_impl.template.dart
index fb13045..701dc3d 100644
--- a/tool/wl_templates/SAL_cs_salvium_interface_impl.template.dart
+++ b/tool/wl_templates/SAL_cs_salvium_interface_impl.template.dart
@@ -555,6 +555,10 @@ class _CsSalviumInterfaceImpl extends CsSalviumInterface {
@override
String getSeed(WrappedWallet wallet) => wallet.actual.getSeed();
+
+ @override
+ Future<void> close(WrappedWallet wallet, {bool save = false}) =>
+ wallet.actual.close(save: save);
}
//END_ON
diff --git a/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart b/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart
index 51c5ef0..b6dee74 100644
--- a/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart
+++ b/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart
@@ -505,6 +505,10 @@ class _CsWowneroInterfaceImpl extends CsWowneroInterface {
@override
String getSeed(WrappedWallet wallet) =>
wallet.get<lib_wownero.Wallet>().getSeed();
+
+ @override
+ Future<void> close(WrappedWallet wallet, {bool save = false}) =>
+ wallet.get<lib_wownero.Wallet>().close(save: save);
}
//END_ON
diff --git a/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart b/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart
index f1d782c..0d4d728 100644
--- a/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart
+++ b/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart
@@ -498,6 +498,10 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface {
@override
String getSeed(WrappedWallet wallet) =>
wallet.get<lib_monero.Wallet>().getSeed();
+
+ @override
+ Future<void> close(WrappedWallet wallet, {bool save = false}) =>
+ wallet.get<lib_monero.Wallet>().close(save: save);
}
//END_ON
Why this scored 42/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.