in GUI mode, only start a limited minimal RPC server

To limit attack surface.

Context:
- both in daemon mode and in GUI mode, we start an RPC server
- the RPC server uses HTTP basic auth, with a random password that is saved in the config file
- read access to the config file implies access to the RPC server
- the traffic is unencrypted
- by default the server listens
  - on Windows, on localhost TCP
  - all other platform, via unix domain sockets
- if an attacker can listen to localhost TCP traffic, and there was traffic
  - they could see the plaintext RPC password and issue their own commands
  - e.g. if wireshark was already installed on the system, this might not require root access
- the "ping" and "gui" commands are used by everyday operations that affect most users:
  - "ping" is used when trying to launch a second instance of electrum, to contact the first instance and enforce "singleton" behaviour
  - "gui" is used for URI handling (`$ xdg-open bitcoin:asdasd`)
- many other sensitive commands, that operate on wallets, require *also* the wallet password
  - but note that wallet.unlock can be used by the user to bypass this and store the wallet password in memory (exposed in GUI)

I propose locking down the RPC server when running in GUI mode:
- we still start it, as it is used for "ping" and "gui" RPCs, however we disable all other RPCs
- we could opt-in enable it, using a config var, except that ofc would not help against an attacker that has filesystem write access to the config file
- so I think it's even safer to just "hardcode" disable it: however the functionality is useful for development
  - I propose we branch based on `constants.net.TESTNET`
  - an alternative we could branch on that is hard to fake is `is_git_clone` in run_electrum
This commit is contained in:
SomberNight
2026-03-25 18:44:56 +00:00
parent bd4439945a
commit d951a3d2f4
2 changed files with 22 additions and 10 deletions
+6 -3
View File
@@ -123,6 +123,7 @@ from electrum.commands import get_parser, get_simple_parser, known_commands, Com
from electrum import daemon
from electrum.util import create_and_start_event_loop, UserFacingException, JsonRPCError
from electrum.i18n import set_language
from electrum import constants
if TYPE_CHECKING:
import threading
@@ -509,7 +510,9 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
configure_logging(config)
fd = daemon.get_file_descriptor(config)
if fd is not None:
d = daemon.Daemon(config, fd, start_network=False)
# When running in GUI mode, only start a limited minimal RPC server, to limit attack surface.
only_minimal_jsonrpc = not constants.net.TESTNET
d = daemon.Daemon(config, fd, start_network=False, only_minimal_jsonrpc=only_minimal_jsonrpc)
try:
d.run_gui()
except BaseException as e:
@@ -535,7 +538,7 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
fd = daemon.get_file_descriptor(config)
if fd is not None:
# run daemon
d = daemon.Daemon(config, fd)
d = daemon.Daemon(config, fd, only_minimal_jsonrpc=False)
d.run_daemon()
sys_exit(0)
else:
@@ -577,7 +580,7 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
except Exception as e:
_logger.exception("error running command (with daemon)")
sys_exit(1)
else:
else: # --offline
if cmd.requires_network:
print_msg("This command cannot be run offline")
sys_exit(1)