aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_identity_seam.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-30 15:48:51 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-30 15:48:51 +0200
commit8926f163dad9d32dc06c3a142658a4e11d9c12c1 (patch)
tree4d36d1c18154cb46e6e80c59ef6c607972caa81e /packages/meshbay-hub/tests/test_identity_seam.py
parent8d96cf2314b45e0737f932998b5422c27a2ae72e (diff)
downloadmeshbay-8926f163dad9d32dc06c3a142658a4e11d9c12c1.tar.gz
refactor(hub): the transport holds an identity, never a private key
Two public keys, sign() and shared(); the apps take transport.signFn. What holds the keys (this page, or the desktop main process) is the identity's business alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_identity_seam.py')
-rw-r--r--packages/meshbay-hub/tests/test_identity_seam.py49
1 files changed, 49 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_identity_seam.py b/packages/meshbay-hub/tests/test_identity_seam.py
new file mode 100644
index 0000000..15db6d8
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_identity_seam.py
@@ -0,0 +1,49 @@
+"""
+Nothing in the interface reads an identity private key except the identity.
+
+The transport sees an identity on a node as two public keys, a signature and an
+X25519 agreement (`transport.js`, `_identityFromKeys`). In a browser that object
+wraps keys held in the page; in the desktop application the keys stay in the
+main process and the same object asks it to sign. That only holds if no other
+code reaches past the object for a key — which is what every admin op, device
+link, chat message and app used to do, twenty times over, and is what this
+test refuses.
+"""
+
+import re
+
+from spa_source import STATIC
+
+# Where a private key may be named: minted and sealed (keyderive.js), wrapped
+# into an identity (transport.js), re-sealed during a passphrase change in a
+# browser (transport-rewrap.js).
+ALLOWED = {"keyderive.js", "transport.js", "transport-rewrap.js"}
+
+
+def test_only_the_identity_touches_a_private_key():
+ offenders = []
+ for path in STATIC.glob("*.js"):
+ if path.name in ALLOWED:
+ continue
+ text = path.read_text(encoding="utf-8")
+ if re.search(r"skEdB64|skXB64|sessionKeys|signBytes\(", text):
+ offenders.append(path.name)
+ assert not offenders, f"these read a private key directly: {offenders}"
+
+
+def test_in_the_transport_only_the_identity_factory_signs_with_a_raw_key():
+ text = (STATIC / "transport.js").read_text(encoding="utf-8")
+ factory = text[text.index("async function _identityFromKeys"):]
+ factory = factory[:factory.index("\n}\n")]
+ rest = text.replace(factory, "")
+ assert "signBytes(" in factory
+ assert "signBytes(" not in rest, "the transport signs with a raw key outside the identity"
+ assert "skXB64" not in rest.replace("id.skXB64", ""), \
+ "the transport reads the X25519 private key outside the identity"
+
+
+def test_a_group_key_is_unwrapped_through_the_identity():
+ crypto = (STATIC / "crypto.js").read_text(encoding="utf-8")
+ unwrap = crypto[crypto.index("async function unwrapGEK"):]
+ unwrap = unwrap[:unwrap.index("\n}\n")]
+ assert "shared(pkEphRaw)" in unwrap and "pkcs8" not in unwrap