ServerConnectWizard: don't set autoconnect on user cancel
What changed, and why it matters
This change fixes a small user-experience bug in Electrum's first-run network setup wizard. Previously, if a user chose to manually configure a server but then closed the wizard without finishing, the wallet would remember that choice and skip the wizard on the next launch, silently connecting to a random server instead. Now the wizard will reappear until the user completes or explicitly accepts the setup. There is no direct theft-of-funds or remote-code-execution vulnerability here.
No urgent security action required. Treat as a normal bug-fix / UX-hardening patch. Users who previously cancelled the wizard and want manual server selection may need to re-run the wizard once after updating.
Security signals we found
Behavioral change in network connection policy
Fixes unintended persistent configuration side effect from UI cancellation
Reduces risk of user being connected to an unexpected/default server without explicit consent
Evidence from the diff
The patch modifies ServerConnectWizard so that the NETWORK_AUTO_CONNECT config key is only written when the user explicitly opts into autoconnect. Before, the ‘welcome’ step’s accept handler (do_configure_autoconnect) wrote False to NETWORK_AUTO_CONNECT as soon as the user checked the custom-server checkbox, even if the user later cancelled the wizard. That caused the wizard not to run on the next startup, forcing the user onto an arbitrary default server. The new do_enable_autoconnect only sets NETWORK_AUTO_CONNECT = True and only when wizard_data[‘autoconnect’] is true; otherwise the config is left unset. Tests are updated to assert that the config value is not set rather than asserting it equals False.
Changed components
electrum/wizard.pytests/test_wizard.pyServerConnectWizard classNETWORK_AUTO_CONNECT configuration handlingInspect captured patch +9 / −7
diff --git a/electrum/wizard.py b/electrum/wizard.py
index 0f55165..a71e2db 100644
--- a/electrum/wizard.py
+++ b/electrum/wizard.py
@@ -826,7 +826,7 @@ class ServerConnectWizard(AbstractWizard):
self.navmap = {
'welcome': {
'next': lambda d: 'proxy_config' if d['want_proxy'] else 'server_config',
- 'accept': self.do_configure_autoconnect,
+ 'accept': lambda d: self.do_enable_autoconnect(d) if d['autoconnect'] else None,
'last': lambda d: bool(d['autoconnect'] and not d['want_proxy'])
},
'proxy_config': {
@@ -869,11 +869,13 @@ class ServerConnectWizard(AbstractWizard):
)
self._daemon.network.run_from_another_thread(self._daemon.network.set_parameters(net_params))
- def do_configure_autoconnect(self, wizard_data: dict):
- self._logger.debug(f'configuring autoconnect: {wizard_data!r}')
+ def do_enable_autoconnect(self, wizard_data: dict):
+ # NETWORK_AUTO_CONNECT will only get explicitly set True, 'autoconnect': False means
+ # the user requested manual server configuration
+ self._logger.debug(f'enabling autoconnect: {wizard_data!r}')
+ assert wizard_data.get('autoconnect'), wizard_data
if self._daemon.config.cv.NETWORK_AUTO_CONNECT.is_modifiable():
- if wizard_data.get('autoconnect') is not None:
- self._daemon.config.NETWORK_AUTO_CONNECT = wizard_data.get('autoconnect')
+ self._daemon.config.NETWORK_AUTO_CONNECT = True
def start(self, *, start_viewstate: WizardViewState = None) -> WizardViewState:
self.reset()
diff --git a/tests/test_wizard.py b/tests/test_wizard.py
index 6bbab88..7c5b740 100644
--- a/tests/test_wizard.py
+++ b/tests/test_wizard.py
@@ -81,7 +81,7 @@ class ServerConnectWizardTestCase(WizardTestCase):
self.assertFalse(w.is_last_view(v_init.view, d))
v = w.resolve_next(v_init.view, d)
self.assertEqual('server_config', v.view)
- self.assertEqual(False, self.config.NETWORK_AUTO_CONNECT)
+ self.assertFalse(self.config.cv.NETWORK_AUTO_CONNECT.is_set())
async def test_proxy(self):
w = ServerConnectWizard(DaemonMock(self.config))
@@ -110,7 +110,7 @@ class ServerConnectWizardTestCase(WizardTestCase):
self.assertFalse(w.is_last_view(v_init.view, d))
v = w.resolve_next(v_init.view, d)
self.assertEqual('proxy_config', v.view)
- self.assertEqual(False, self.config.NETWORK_AUTO_CONNECT)
+ self.assertFalse(self.config.cv.NETWORK_AUTO_CONNECT.is_set())
d_proxy = {'enabled': False}
d.update({'proxy': d_proxy})
v = w.resolve_next(v.view, d)
Why this scored 20/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.