commands: add clear error that plugin commands cannot be run with -o
What changed, and why it matters
This commit fixes a crash in Electrum's command-line tool. When a user ran a plugin-provided command in offline mode (using the -o flag), the program tried to access a daemon object that didn't exist, causing an ugly AttributeError. The patch makes two changes: it automatically marks all plugin commands as requiring a daemon, and it adds an explicit check so the program fails cleanly if a plugin command is somehow run without a daemon. This is a robustness/usability fix rather than a security vulnerability.
No urgent action needed. This is a minor hardening/UX fix. Users and downstream packagers can treat it as a routine bugfix. If backporting, include it with other CLI robustness improvements.
Security signals we found
Crash/DoS-like condition in CLI from unhandled AttributeError
Defensive assertion added to prevent null pointer dereference equivalent
No evidence of malicious exploitation path
Evidence from the diff
The change is in electrum/commands.py. The plugin_command decorator now appends ‘n’ (requires daemon/network) to the command flags string if not already present, and the func_wrapper now asserts daemon is not None before dereferencing daemon._plugins. Previously, running a plugin command with -o (offline/no daemon) caused daemon to be None and produced an AttributeError on daemon._plugins. The fix prevents the crash and gives a clearer error path.
Changed components
electrum/commands.pyElectrum CLI plugin commandsrun_electrum offline mode (-o)Inspect captured patch +5 / −1
diff --git a/electrum/commands.py b/electrum/commands.py
index 9d2cf9a..f5c4887 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -114,7 +114,7 @@ def format_satoshis(x: Union[float, int, Decimal, None]) -> Optional[str]:
class Command:
def __init__(self, func, name, s):
self.name = name
- self.requires_network = 'n' in s
+ self.requires_network = 'n' in s # better name would be "requires daemon"
self.requires_wallet = 'w' in s
self.requires_password = 'p' in s
self.requires_lightning = 'l' in s
@@ -2198,6 +2198,9 @@ class Commands(Logger):
def plugin_command(s, plugin_name):
"""Decorator to register a cli command inside a plugin. To be used within a commands.py file
in the plugins root."""
+ # atm all plugin commands require a daemon, cannot be run in 'offline' mode:
+ if 'n' not in s:
+ s += 'n'
def decorator(func):
assert len(plugin_name) > 0, "Plugin name must not be empty"
func.plugin_name = plugin_name
@@ -2211,6 +2214,7 @@ def plugin_command(s, plugin_name):
async def func_wrapper(*args, **kwargs):
cmd_runner = args[0] # type: Commands
daemon = cmd_runner.daemon
+ assert daemon is not None
kwargs['plugin'] = daemon._plugins.get_plugin(plugin_name)
return await func(*args, **kwargs)
Why this scored 22/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.