plugins: use decorator to early return if plugin not authorized
What changed, and why it matters
This commit tightens Electrum's plugin authorization checks. Previously, some plugin-loading paths could execute parts of a plugin even if the user had not authorized it. The change adds early 'return' or 'assert' checks so unauthorized plugins are skipped before their code is loaded or run. This reduces the chance that a malicious or unwanted plugin could activate without explicit permission.
Treat this as a security-hardening patch and include it in the next release. Review whether prior versions allowed unauthorized external plugins to execute initialization code or register hardware-wallet support, and consider issuing an advisory if exploitable behavior is confirmed.
Security signals we found
Authorization bypass hardening
Defense in depth for plugin loading
Potential security fix for unauthorized plugin execution
No CVE or vendor security advisory referenced in commit
Evidence from the diff
The patch in electrum/plugin.py adds authorization guards to plugin lifecycle methods. maybe_load_plugin_init_method and load_plugin_by_name now return early if self.is_authorized(name) is false. get_hardware_support skips unauthorized hardware-wallet plugins. get_plugin asserts authorization before loading. The previous code only checked authorization after partially loading external plugins, and allowed non-authorized plugins to remain in self._hw_wallets. The change centralizes the authorization decision and prevents execution of plugin init/gui modules before the user has approved the plugin.
Changed components
electrum/plugin.pyPlugin loading and authorization subsystemHardware wallet plugin enumerationInspect captured patch +10 / −3
diff --git a/electrum/plugin.py b/electrum/plugin.py
index 3a7892b..ed1bbae 100644
--- a/electrum/plugin.py
+++ b/electrum/plugin.py
@@ -589,6 +589,8 @@ class Plugins(DaemonThread):
def maybe_load_plugin_init_method(self, name: str) -> None:
"""Loads the __init__.py module of the plugin if it is not already loaded."""
+ if not self.is_authorized(name):
+ return
base_name = ('electrum_external_plugins.' if self.is_external(name) else 'electrum.plugins.') + name
if base_name not in sys.modules:
metadata = self.get_metadata(name)
@@ -609,14 +611,13 @@ class Plugins(DaemonThread):
self.exec_module_from_spec(init_spec, base_name)
def load_plugin_by_name(self, name: str) -> 'BasePlugin':
+ if not self.is_authorized(name):
+ return
if name in self.plugins:
return self.plugins[name]
# if the plugin was not enabled on startup the init module hasn't been loaded yet
self.maybe_load_plugin_init_method(name)
is_external = self.is_external(name)
- if is_external and not self.is_authorized(name):
- self.logger.info(f'plugin not authorized {name}')
- return
if not is_external:
full_name = f'electrum.plugins.{name}.{self.gui_name}'
else:
@@ -728,6 +729,11 @@ class Plugins(DaemonThread):
def get_hardware_support(self):
out = []
for name, details in self._hw_wallets.items():
+ if not self.is_authorized(name):
+ # we allow non-authorized plugins to populate self._hw_wallets
+ # so that a plugin can be loaded and authorized without having
+ # to start a new session
+ continue
try:
p = self.get_plugin(name)
if p.is_available():
@@ -766,6 +772,7 @@ class Plugins(DaemonThread):
register_keystore(details[1], dynamic_constructor)
def get_plugin(self, name: str) -> 'BasePlugin':
+ assert self.is_authorized(name)
if name not in self.plugins:
self.load_plugin(name)
return self.plugins[name]
Why this scored 65/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.