daemon: forbid "setconfig" command to change rpcserver settings in-flight
What changed, and why it matters
This commit fixes a security issue in Electrum's background daemon (the program that stays running to serve wallet requests). Previously, a user could run a command that would instantly strip the RPC password from a running daemon, leaving it unauthenticated for the current session. The daemon would then accept commands from any local program without a password. On the next restart, Electrum would silently generate a new random password, which could lock out legitimate tools. The patch now forbids changing RPC server settings while the daemon is running and ensures a password is always required.
Users running Electrum daemon should upgrade to a version containing this commit. Until patched, avoid running 'electrum setconfig rpcpassword ""' or similar commands against a live daemon, and restrict local access to the RPC port/socket. Wallet applications and integrations that rely on the Electrum RPC should verify they are using the configured credentials and not assume authentication can be disabled.
Security signals we found
Authentication bypass via empty RPC password
In-flight weakening of daemon security settings
Inconsistent security state across daemon restart
Local privilege escalation / unauthorized local wallet access
Patch explicitly forbids live RPC config changes and ensures password is always set
Evidence from the diff
The patch addresses two related RPC daemon issues. First, _setconfig in commands.py no longer permits live changes to RPC_USERNAME, RPC_PASSWORD, RPC_HOST, RPC_PORT, RPC_SOCKET_TYPE, or RPC_SOCKET_FILEPATH when a daemon is already running; it raises a UserFacingException instead. Previously, setting rpcpassword to an empty string would immediately set self.daemon.commands_server.rpc_password = ‘’, which the AuthenticatedServer.authenticate() method treated as ‘authentication disabled’. Second, daemon.py now treats an empty/unset rpc_password as an error condition rather than a disabled-auth signal, and get_rpc_credentials() already generates a 128-bit random password when none is configured. This closes a window where the daemon could be running with no RPC authentication.
Changed components
electrum/commands.py:_setconfigelectrum/daemon.py:get_rpc_credentialselectrum/daemon.py:AuthenticatedServer.authenticateInspect captured patch +16 / −7
diff --git a/electrum/commands.py b/electrum/commands.py
index dca66ed..021041b 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -397,10 +397,19 @@ class Commands(Logger):
def _setconfig(self, key, value):
value = self._setconfig_normalize_value(key, value)
- if self.daemon and key == SimpleConfig.RPC_USERNAME.key():
- self.daemon.commands_server.rpc_user = value
- if self.daemon and key == SimpleConfig.RPC_PASSWORD.key():
- self.daemon.commands_server.rpc_password = value
+ if self.daemon and key in (
+ SimpleConfig.RPC_USERNAME.key(),
+ SimpleConfig.RPC_PASSWORD.key(),
+ SimpleConfig.RPC_HOST.key(),
+ SimpleConfig.RPC_PORT.key(),
+ SimpleConfig.RPC_SOCKET_TYPE.key(),
+ SimpleConfig.RPC_SOCKET_FILEPATH.key(),
+ ):
+ raise UserFacingException(
+ "error: RPC server settings cannot be changed for already running daemon. "
+ "Stop the daemon first, and run 'setconfig' in --offline mode. "
+ "\nFor example: '$ electrum -o setconfig rpcport 7777'."
+ )
if Plugins.is_plugin_enabler_config_key(key):
self.config.set_key(key, value)
else:
diff --git a/electrum/daemon.py b/electrum/daemon.py
index 818b327..ceab53c 100644
--- a/electrum/daemon.py
+++ b/electrum/daemon.py
@@ -180,6 +180,7 @@ def wait_until_daemon_becomes_ready(*, config: SimpleConfig, timeout=5) -> bool:
def get_rpc_credentials(config: SimpleConfig) -> Tuple[str, str]:
rpc_user = config.RPC_USERNAME or None
rpc_password = config.RPC_PASSWORD or None
+ # note: we explicitly forbid empty/unset password, and will generate one now instead
if rpc_user is None or rpc_password is None:
rpc_user = 'user'
bits = 128
@@ -219,9 +220,8 @@ class AuthenticatedServer(Logger):
self._methods[name] = f
async def authenticate(self, headers):
- if self.rpc_password == '':
- # RPC authentication is disabled
- return
+ if not self.rpc_password:
+ raise Exception('Server RPC password is unset. This should not happen.')
auth_string = headers.get('Authorization', None)
if auth_string is None:
raise AuthenticationInvalidOrMissing('CredentialsMissing')
Why this scored 63/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.