summaryrefslogtreecommitdiffstats
path: root/devel-phases-next.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-14 01:28:12 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-14 01:28:12 +0200
commitd7120761fe8cf406f374ef769db6e1f9bf1fe287 (patch)
treecc73da554575c51d7ee3837085a4318e08c610aa /devel-phases-next.md
parent8f6e2f724fd24a077de11d4a3b3ae069d369324d (diff)
downloadmeshbay-d7120761fe8cf406f374ef769db6e1f9bf1fe287.tar.gz
docs: record the invite redesign — H3 and M3 closed
draft-v5 §2: against an active hub, reading content moves from "❌ H3" to "❌ T3 (browser) · ✅ native". The defensible sentence becomes "the hub cannot read your content unless it ships you malicious client code" — T3 is now the only path, it is an artifact rather than a silent directory lie, and it does not exist for a native client. New §5.5 describes admission and key delivery, with the four properties that carry it and the one exception (open-join groups, where the hub can walk in the front door — a property of open joining, and the setting is read from node.toml). Corrected while writing it: §5.1 said the C5b fix stopped a group admin who does not run the node from inviting, and that the redesign reverses this. It does not, because delegation was deferred. What changed is the timing — the operator issues a code and is then out of the loop. devel-phases-next: 12.1 is done and NOT as written. The plan was key transparency plus safety numbers; what shipped removes the directory read instead. Safety numbers make substitution detectable by a human who checks, at first contact, when there is nothing to check against. 12.2 (served-SPA integrity) is now the highest-value item in that phase. Phase 14 marked for what landed. second-review: H3 and M3 annotated closed at the finding, with what actually closed them. The §7 verdict table is left intact — it is the record of an audit on a date, and falsifying it would be worse than leaving it — with a note pointing at draft-v5 §2 for current state. CLAUDE.md matters most here, being loaded every session: NS4 read "admin_pk_ed25519 auto-pinned from keystore ✅ DONE", which is M3 described as a feature. Rewritten, with the two fixes that must never be attempted (auto-pin, hub lookup). QE/deploy/README.md: set-admin-pk retired from the walkthrough; the regression checklist now exercises pairing, joining by code, recognition without a code, and revocation. USERGUIDE.md is beyond the invite work but was actively wrong: it told users to POST GEK bundles to a hub endpoint deleted in Phase 12, and to re-wrap for every remaining member on revocation. Both replaced with what the code does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'devel-phases-next.md')
-rw-r--r--devel-phases-next.md72
1 files changed, 47 insertions, 25 deletions
diff --git a/devel-phases-next.md b/devel-phases-next.md
index 682cb1b..3c9c639 100644
--- a/devel-phases-next.md
+++ b/devel-phases-next.md
@@ -745,29 +745,38 @@ cannot delete or overwrite another member's file, and cannot change the group ke
> opaque private-group metadata, chat_notify minimization, schema cleanup) is dropped
> from the plan; the swarm item already shipped in 11.5.18.
-**Objective:** make the hub's two remaining powers over confidentiality *detectable*,
-given that it stays in the trusted path by choice.
+**Objective:** make the hub's remaining power over confidentiality *detectable*, given
+that it stays in the trusted path by choice.
-### Why these two survive
+### 12.1 is DONE — and not as it was written
-**H3 is the last open High finding, and nothing else fixes it.** The hub is the public
-key directory: when a member invites someone, the inviter fetches the invitee's
-`pk_x25519` from the hub and wraps the GEK for it. A hub that returns its own key gets
-the group key, decrypts everything, and nothing in the protocol notices. This needs no
-JWT forgery and no code injection. Deferring Phase 12 wholesale would leave it open
-indefinitely, so it moves here rather than disappearing.
+**H3 is closed (2026-08-14), by removing the lookup rather than by verifying it.** The
+plan here was key transparency and safety numbers: keep fetching the invitee's key from
+the hub, and give humans a way to notice a substitution. What shipped instead is the
+invite redesign in `docs/invite-pairing-v1.md` — the node holds the GEK and wraps it
+itself, for a key the recipient proves possession of, and identities are bound to
+accounts by one-time codes the hub never sees.
-**Serving the SPA is now a deliberate choice, not a residual risk.** A hub that ships
-the code can exfiltrate keys from the page whatever the protocol does (T3). That is
-accepted — but it should be labelled honestly and made verifiable where possible.
+Why that is better than what was planned: safety numbers make a substitution *detectable
+by a human who bothers to check*, at the single worst moment (first contact, when there
+is no previous key to compare against). Removing the directory read from the invite path
+makes the substitution impossible instead, and costs the user one code to pass along
+rather than a fingerprint comparison ritual.
+
+It also closed **M3** as a side effect, and absorbed **14.3/14.4** (CLI invite, member
+management), which had to exist for a headless operator to admit anyone.
+
+Safety numbers may still return later as defence in depth for *identity* verification —
+"is this really Bob's account" — which is a different question from "which key gets the
+group key". They are no longer load-bearing.
### Milestones
| # | Component | Description |
|---|---|---|
-| 12.1 | **Key transparency + safety numbers** [H3] | Hub-signed append-only key log; clients pin the key they first saw for a contact and audit the log; a key change raises a blocking warning before any GEK is wrapped for it; safety-number comparison UI between two members. Applies to the SPA and the native client alike |
-| 12.2 | Served-SPA integrity | Strict CSP, Subresource Integrity on the bundle, and a signed digest of the served bundle published by the hub so a native client or extension can verify what the browser was given |
-| 12.3 | Honest labelling | `/app/` states plainly that the hub serves this code and what that implies. Docs stop claiming end-to-end integrity for the hub-served path — the claim that holds is "the hub cannot read your content unless it actively attacks you" |
+| 12.1 | ~~Key transparency + safety numbers~~ [H3] | ✅ **DONE 2026-08-14**, by a different design — see above and `docs/invite-pairing-v1.md` |
+| 12.2 | Served-SPA integrity | Strict CSP, Subresource Integrity on the bundle, and a signed digest of the served bundle published by the hub so a native client or extension can verify what the browser was given. **Now the highest-value item here**: T3 is the only remaining way an active hub reads content, and it can also lift a pairing code out of the page it served |
+| 12.3 | Honest labelling | `/app/` states plainly that the hub serves this code and what that implies. Docs stop claiming end-to-end integrity for the hub-served path — the claim that holds is "the hub cannot read your content unless it ships you malicious client code" |
| 12.4 | Written threat model | One page: passive hub, active hub, malicious node operator, malicious member, network attacker, local attacker — and for each claim, which adversary it holds against. This is what stops the overclaiming pattern the second review kept finding |
**Dropped from the old Phase 12** (recorded so the intent is not lost if it returns):
@@ -866,8 +875,13 @@ Every operator action lived behind a web UI on the node's own loopback interface
so a node on a server reached over SSH could not be operated at all without
port-forwarding a browser session — and 11.5.3 added a token that had to be
copied out of a log to get in. `status`, `ui` and `gek-init` shipped to unblock
-that. The remaining commands matter for the same reason: **a headless operator
-still cannot invite a member or delete a file without a browser.**
+that.
+
+**Member management landed 2026-08-14** with the invite redesign, for the same
+reason: a node admits people from its own roster, and a headless operator had no
+way to put anyone on it. `operator pair`, `member list|invite|revoke|unpin` all
+work over SSH. **Deleting a file is now the only operator action that still needs
+a browser.**
### Milestones
@@ -879,19 +893,27 @@ still cannot invite a member or delete a file without a browser.**
| 14.2 | `meshbay-node group list` | List configured groups with online status |
| 14.3 | `meshbay-node group create` | Create group on hub, add to config, generate GEK |
| 14.4 | `meshbay-node group join` | Join existing group, fetch GEK from local BundleStore, add to config |
-| 14.5 | `meshbay-node member invite` | Wrap GEK for new member, store bundle in the local BundleStore (**not** the hub — bundles have been P2P since Phase 12) |
-| 14.6 | `meshbay-node member remove` | Rotate GEK, re-wrap for remaining members, store locally |
-| 14.7 | `meshbay-node member list` | List group members with online status |
+| 14.5 | `meshbay-node member invite` | ✅ **DONE 2026-08-14** — issues a one-time code; the node wraps the GEK itself when the invitee connects. The original description ("wrap GEK for new member, store bundle") describes the design the invite redesign replaced |
+| 14.6 | `meshbay-node member revoke` | ✅ **DONE** — stops the node serving the key, and tells the operator to rotate it, since the ex-member still holds the current one |
+| 14.6b | `meshbay-node member unpin` | ✅ **DONE** — forget a pinned identity so someone can pair again after a key reset |
+| 14.7 | `meshbay-node member list` | ✅ **DONE** — roster: who is admitted, with what role, pinned when and how. Online status still to add |
| 14.8 | Config reload (SIGHUP) | Daemon reloads config and adds/removes groups without restart |
-| 14.9 | `meshbay-node admin-key` | Pin the operator's **client** Ed25519 key as `admin_pk_ed25519` — fixes M3, where auto-pinning the node keystore key makes operator deletion impossible |
+| 14.9 | ~~`meshbay-node admin-key`~~ | ✅ **Superseded by `operator pair`** — pairing binds the operator's browser key with a one-time code instead of pasting a base64 key, and the auto-pin that made M3 possible is deleted |
| 14.10 | `meshbay-node denylist` | Inspect and clear the persisted revocation denylist (11.5.17) |
+| 14.11 | `meshbay-node file rm` | The one operator action still requiring a browser |
### Architecture
-CLI commands communicate with the running daemon via a local Unix socket
-(`/run/meshbay-node.sock`, mode 0600, owner-only). The daemon exposes a small internal API
-for status queries and management operations. If the daemon is not running,
-commands that require it fail with a clear error.
+CLI commands talk to the running daemon over its **loopback admin API**, authenticated
+with the per-run session token (11.5.3) — `_daemon_api()` in `daemon.py`. The Unix-socket
+design below was the original plan; the loopback API already existed for the admin UI,
+carries the same authorization, and avoided a second control plane. A socket would still
+be an improvement (no port, file permissions instead of a token file) if the admin UI
+ever goes away.
+
+`status` deliberately does *not* use it: it reads the keystore, the config and the roster
+directly, so it works while the daemon is stopped — which is when an operator most needs
+to know why.
---