qml: add wallets search option, allow loading hidden wallet iff search string matches exact wallet name
What changed, and why it matters
This commit adds a search box to Electrum's mobile-style QML wallet list. It also changes how 'hidden' wallets (those whose filenames begin with a dot) are handled: previously they were blocked entirely, now they are allowed but kept out of the normal list. A user can load a hidden wallet only by typing its exact name into the search box. The change appears to be a deliberate feature, not a fix for an active security bug, but it slightly widens what wallet files the GUI will load.
Review as a normal feature change. Verify that hidden wallets still require the usual password/seed to open and that the exact-name match prevents unintended disclosure. No urgent security action is indicated by the diff alone.
Security signals we found
GUI now permits loading wallets with dot-prefixed filenames that are otherwise hidden from the list
Exact-name match is required to load a hidden wallet, reducing accidental or brute-force discovery
Validation still rejects '..' prefixed filenames and path separators
No authentication or password prompt changes are visible in the diff
No explicit security bug or CVE reference in commit message or diff
Evidence from the diff
The patch modifies the QML wallet chooser and its Python model. QEWalletListModel now includes dot-prefixed files when scanning the wallet directory. Wallets.qml hides wallets whose names start with ‘.’ unless they are already active, and adds a TextField whose ‘onAccepted’ handler loads a wallet if the typed string exactly matches an available wallet name via pathForName(). QEDaemon validation loosens the prohibition on dot-prefixed wallet names, only rejecting ‘..’ prefixed names. The intent is to support hidden wallets that can be created and opened only by exact name.
Changed components
electrum/gui/qml/components/Wallets.qmlelectrum/gui/qml/qedaemon.pyQML wallet list UIQEWalletListModel wallet scanning logicQEDaemon wallet name validationInspect captured patch +51 / −5
diff --git a/electrum/gui/qml/components/Wallets.qml b/electrum/gui/qml/components/Wallets.qml
index e98e82c..21f4626 100644
--- a/electrum/gui/qml/components/Wallets.qml
+++ b/electrum/gui/qml/components/Wallets.qml
@@ -37,6 +37,38 @@ Pane {
text: qsTr('Wallets')
}
+ TextField {
+ id: searchEdit
+ Layout.fillWidth: true
+ Layout.leftMargin: constants.paddingLarge
+ Layout.rightMargin: constants.paddingLarge
+
+ placeholderText: qsTr('search')
+ inputMethodHints: Qt.ImhNoPredictiveText
+
+ onAccepted: {
+ // load a wallet (e.g. a hidden wallet not shown in the list) when
+ // the search text exactly matches an available wallet name
+ var path = Daemon.availableWallets.pathForName(text)
+ if (path && !Daemon.loading) {
+ if (!Daemon.currentWallet || Daemon.currentWallet.name != text) {
+ Daemon.loadWallet(path)
+ } else {
+ app.stack.pop()
+ }
+ }
+ }
+
+ Image {
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.rightMargin: constants.paddingMedium
+ source: Qt.resolvedUrl('../../icons/zoom.png')
+ sourceSize.width: constants.iconSizeMedium
+ sourceSize.height: constants.iconSizeMedium
+ }
+ }
+
Frame {
id: detailsFrame
Layout.fillWidth: true
@@ -52,9 +84,14 @@ Pane {
model: Daemon.availableWallets
delegate: ItemDelegate {
+ property bool matchesSearch: searchEdit.text.length === 0
+ || model.name.toLowerCase().indexOf(searchEdit.text.toLowerCase()) !== -1
+ property bool hiddenWallet: model.name.startsWith('.') && !model.active
width: ListView.view.width
- height: row.height
-
+ height: visible ? row.height : 0
+ // visible: searchEdit.text.length === 0
+ // || model.name.toLowerCase().indexOf(searchEdit.text.toLowerCase()) !== -1
+ visible: matchesSearch && !hiddenWallet
onClicked: {
if (!Daemon.currentWallet || Daemon.currentWallet.name != model.name) {
if (!Daemon.loading) // wallet load in progress
diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py
index 3b3ea75..679bf5a 100644
--- a/electrum/gui/qml/qedaemon.py
+++ b/electrum/gui/qml/qedaemon.py
@@ -72,7 +72,7 @@ class QEWalletListModel(QAbstractListModel):
wallet_folder = os.path.dirname(self.daemon.config.get_wallet_path())
with os.scandir(wallet_folder) as it:
for i in it:
- if i.is_file() and not i.name.startswith('.'):
+ if i.is_file():
available.append(i.path)
for path in sorted(available):
wallet = self.daemon.get_wallet(path)
@@ -109,6 +109,13 @@ class QEWalletListModel(QAbstractListModel):
return True
return False
+ @pyqtSlot(str, result=str)
+ def pathForName(self, name):
+ for wallet_name, wallet_path in self._wallets:
+ if name == wallet_name:
+ return wallet_path
+ return ''
+
@pyqtSlot(str)
def updateWallet(self, path):
i = 0
@@ -325,8 +332,10 @@ class QEDaemon(AuthMixin, QObject):
for forbidden_char in ("/", "\\", ):
if forbidden_char in wallet_name:
return False
- if wallet_name.startswith('.'): # not shown in wallet list
- # TODO: allow wallets starting with '.' as hidden wallets, opened e.g. through wallet creation wizard
+ # note: wallet names starting with '.' are allowed as hidden wallets; they are not shown
+ # in the wallet list unless active (see Wallets.qml) but can be created via the wizard.
+ # disallow double '..*' filenames though.
+ if wallet_name.startswith('..'):
return False
if os.path.basename(wallet_name) != wallet_name: # '/foo/bar/' returns 'bar'
return False
Why this scored 29/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.