json_db: escape '\' and '~' in json patch pointer
What changed, and why it matters
This commit fixes a bug in how Electrum builds JSON Patch pointers when wallet data contains special characters like '/' or '~' in keys. Before the fix, these characters were not escaped, which could cause patch operations to target the wrong location in the wallet database or fail. The fix follows RFC 6901 by escaping '~' as '~0' and '/' as '~1'. This is a data-integrity bug in wallet storage synchronization, but the commit itself does not describe it as a security vulnerability.
Treat as a bug fix with possible data-integrity/security implications. Review whether unescaped patches could have caused wallet state corruption or unexpected behavior in prior versions, especially for wallets with labels or custom keys containing '/' or '~'. No immediate exploit mitigation is described, but users relying on such keys should update.
Security signals we found
Incorrect JSON Pointer escaping can misroute patch operations
Potential data corruption or loss in wallet database updates
No explicit security framing in commit message or diff
Fixes a standards-compliance bug (RFC 6901)
Evidence from the diff
In electrum/json_db.py, the key_path() helper now escapes string keys using jsonpointer.escape() before assembling JSON Pointer paths. Previously, keys containing ‘/’ or ‘~’ were inserted literally, violating RFC 6901 and causing emitted JSON Patches to reference incorrect paths. A regression test verifies that keys such as ‘some/label~key’ and ‘x1/’ produce escaped pointers ‘/labels/some~1label~0key’ and ‘/x1~1’. The change is small (+13/-1) and defensive.
Changed components
electrum/json_db.pyJSON Patch generation for wallet storageKeys containing '/' or '~' in wallet data dictionariesInspect captured patch +13 / −1
diff --git a/electrum/json_db.py b/electrum/json_db.py
index dbf3c3d..5af1966 100644
--- a/electrum/json_db.py
+++ b/electrum/json_db.py
@@ -62,7 +62,7 @@ def key_path(path: Sequence[_FLEX_KEY], key: _FLEX_KEY) -> str:
return str(int(x))
else:
assert isinstance(x, str), f"unexpected key type for: {x!r}"
- return x
+ return jsonpointer.escape(x) # RFC 6901: escape '~' and '/'
items = [to_str(x) for x in path]
if key is not None:
items.append(to_str(key))
diff --git a/tests/test_jsondb.py b/tests/test_jsondb.py
index bb07eaf..076e565 100644
--- a/tests/test_jsondb.py
+++ b/tests/test_jsondb.py
@@ -152,3 +152,15 @@ class TestJsonDB(ElectrumTestCase):
jpatch = jsonpatch.JsonPatch(patches)
data = jpatch.apply(data)
self.assertEqual(data, {'d': 3})
+
+ async def test_jsondb_pointer_escaping(self):
+ # keys containing '/' or '~' must be escaped per RFC 6901 in emitted patches
+ data = {'labels': {'some/label~key': 'hello'}, 'x1/': {'type': 'bip32'}}
+ db = JsonDB(json.dumps(data))
+ labels = db.get_dict('labels')
+ labels['some/label~key'] = 'world'
+ db.data.pop('x1/')
+ patches = json.loads('[' + ','.join(db.pending_changes) + ']')
+ self.assertEqual({'/labels/some~1label~0key', '/x1~1'}, {p['path'] for p in patches})
+ data = jsonpatch.JsonPatch(patches).apply(data)
+ self.assertEqual({'labels': {'some/label~key': 'world'}}, data)
Why this scored 59/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.