constants: add basic sanity check for servers.json
What changed, and why it matters
This commit adds a basic sanity check to Electrum's code that reads the built-in list of default servers (servers.json). Previously, if a user edited that JSON file incorrectly, the bad data would travel deeper into the program and cause a confusing crash later. Now the code checks early that each server entry is a dictionary and that all values inside it are strings, failing fast with a clearer error message. It is a hardening/quality improvement, not a fix for an externally exploitable vulnerability.
Treat as routine hardening. No urgent action required. If backporting, include as part of a general robustness patch set. Users should not manually edit bundled servers.json unless they understand the expected format.
Security signals we found
Input validation added to bundled configuration data
Early-fail assertion to prevent downstream type errors
Commit message explicitly describes user error handling, not a security vulnerability
Evidence from the diff
The change modifies AbstractNet.DEFAULT_SERVERS() in electrum/constants.py. After reading servers.json, it now iterates over the parsed dictionary and asserts (1) each value is a dict and (2) all nested values are strings. This converts latent data-format errors into early, explicit assertion failures. The deepcopy behavior is preserved. There is no evidence this addresses an attacker-controlled input path: servers.json is a bundled file, and the commit message frames the issue as user-modification causing obscure crash reports.
Changed components
electrum/constants.pyAbstractNet.DEFAULT_SERVERS classpropertyInspect captured patch +7 / −3
diff --git a/electrum/constants.py b/electrum/constants.py
index 6026b9c..7fc965e 100644
--- a/electrum/constants.py
+++ b/electrum/constants.py
@@ -100,9 +100,13 @@ class AbstractNet:
def DEFAULT_SERVERS(cls) -> Mapping[str, Mapping[str, str]]:
if cls._cached_default_servers is None:
default_file = {} if cls.TESTNET else None # for mainnet we hard-fail if the file is missing.
- cls._cached_default_servers = read_json(os.path.join('chains', cls.NET_NAME, 'servers.json'), default_file)
- d = cls._cached_default_servers
- return copy.deepcopy(d)
+ d = read_json(os.path.join('chains', cls.NET_NAME, 'servers.json'), default_file)
+ # sanity check
+ for k, v in d.items():
+ assert isinstance(v, dict), f'value for {k} not a dict in servers.json'
+ assert all(isinstance(v2, str) for v2 in v.values()), f'non-str values for key {k} in servers.json'
+ cls._cached_default_servers = d
+ return copy.deepcopy(cls._cached_default_servers)
_cached_fallback_lnnodes = None
@classproperty
Why this scored 23/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.