From 422647e7898b8df67cd11f8fb060aec23dec84aa Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 14 Oct 2026 22:20:15 +0200 Subject: feat(cli): meshbay-cli, a command-line client for members Signs in to the hub, lists the account's groups and their folders, and downloads and uploads files directly with each group's node, end-to-end encrypted. With no argument it opens a two-pane interactive mode (Textual). Session and device keys are kept encrypted under the system's key storage, or a 0600 key file where there is none; node keys are pinned at first contact; a new machine links as a device. Packaged on its own on Linux (deb and rpm, on meshbay-common's venv, with a man page generated from the parser) and frozen next to meshbay-node in the full Windows installer, not the light one, with a self-test the build runs. client/keys.py ports keyderive.js and is LGPL like it. Co-Authored-By: Claude Opus 5.5 --- docs/MESHBAY_DESIGN.md | 114 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 93 insertions(+), 21 deletions(-) (limited to 'docs/MESHBAY_DESIGN.md') diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index 2174879..6dd1b2e 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -16,7 +16,7 @@ > them — it names the invariant that holds today, not the incident that produced > it. §13 is the register of those labels. > -> Wire versions at the time of writing: **MNP 6.2** (oldest peer accepted 4.0), +> Wire versions at the time of writing: **MNP 6.3** (oldest peer accepted 4.0), > **MHP 0.1**, packages **0.19.0**. The normative source for the wire format is > `MESHBAY_NODE_PROTOCOL.md`; this document states the design the protocol > serves, not its byte layout. @@ -302,6 +302,7 @@ comes from the hash binding: | Bound | Value | |---|---| | Request TTL | 1 h, `[node] device_request_ttl_minutes` | +| Pending requests per account per node | 10; filing one more drops the oldest | | Devices per account per node | 5 | | Attempts per connection | 5, audited on exhaustion | | Filing a request | requires the account to have at least one pinned identity already | @@ -1105,15 +1106,22 @@ legitimate rotation. > complete a handshake with. It is unverified at that point and is never a > substitute for the ack. -**The pre-proof window is three messages, and that is a bound to defend.** Only -the two bundle fetches and `join_request` are served before the proof, because -each is something a caller needs *in order to* prove possession of the group key: -a returning browser has to recover its identity, and a new member has no key to -prove with. Nothing else qualifies. Device messages are **authenticated-only** — -a device request is countersigned later by a device already pinned, so requiring -the caller to finish its own handshake first costs nothing and keeps the surface -at three. The bundle fetches are additionally bounded (4 per connection) and -audited, because that window is the neighbourhood **C4** and **C5b** came from. +**The pre-proof window is four messages, and that is a bound to defend.** Only +the two bundle fetches, `join_request` and `device_add_request` are served before +the proof, because each is something a caller needs *in order to* prove possession +of the group key: a returning browser has to recover its identity, a new member +has no key to prove with, and a new device of a known member has none either — the +group key is wrapped for an identity the node pinned, and a machine that keeps no +bundle there (the desktop application, the command-line client, a browser with +browser access off) holds no such identity until another device approves it. +Nothing else qualifies. Filing a device request is **inert**: nothing is admitted +until a device already pinned countersigns it, the code is bound to the new keys, +the request is bounded per connection (5) and per account (10 pending), and it is +audited. Every other device message — looking up, approving, listing, revoking, +saying hello — is sent by a device that can finish its own handshake, and is +**authenticated-only**. The bundle fetches are additionally bounded (4 per +connection) and audited, because that window is the neighbourhood **C4** and +**C5b** came from. **Pinning is defence in depth, not the primary control.** A substituted node already fails the key proof. Pinning covers the case where an attacker *holds* the @@ -2610,16 +2618,16 @@ The rules that make this safe: ## 8. Clients -### 8.1 Two clients, deliberately +### 8.1 The clients, deliberately different -| | Hub-served web SPA | Desktop client | -|---|---|---| -| Distribution | served by the hub | installed, signed release | -| Code integrity | **T3 accepted** — the hub can inject | detectable *if* reproducible builds ship | -| Key storage | IndexedDB / sessionStorage, plus a bundle on each node | OS-protected local storage; keys never bundled | -| Transport | WebRTC | WebRTC **+ QUIC** (sidecar, for hub-less `group://`) | -| Downloads | to disk where the engine allows it | native, streamed, unlimited | -| Positioning | **convenience tier** — zero install | recommended for sensitive use | +| | Hub-served web SPA | Desktop client | Command-line client (§8.6) | +|---|---|---|---| +| Distribution | served by the hub | installed, signed release | installed: its own `.deb`/`.rpm`, inside Windows Full and Store | +| Code integrity | **T3 accepted** — the hub can inject | detectable *if* reproducible builds ship | as the desktop client: the hub serves it no code | +| Key storage | IndexedDB / sessionStorage, plus a bundle on each node | OS-protected local storage; keys never bundled | OS key storage, else a 0600 file; opens a node's bundle, never writes one | +| Transport | WebRTC | WebRTC **+ QUIC** (sidecar, for hub-less `group://`) | WebRTC | +| Downloads | to disk where the engine allows it | native, streamed, unlimited | native, resumable, unlimited | +| Positioning | **convenience tier** — zero install | recommended for sensitive use | a member's files from a terminal, scripts and headless machines | **The SPA is not deprecated and stays.** It is the zero-install path, and the objective is explicit: **a native client must not prevent web use.** It must be @@ -2995,6 +3003,67 @@ MSE string) fed to a source buffer, with the node holding one slot per viewer. --- +### 8.6 The command-line client + +`meshbay-cli` (`packages/meshbay-cli`) is a member's files from a terminal: +`login`, `logout`, `groups`, `ls`, `get`, `put`, `trust`, and with no argument +an interactive two-pane view (Textual) over the same library. It is not an +operator tool — a node is administered through `meshbay-node` and its loopback +API (§6.7) — and it does not stream, play or chat: those belong to an engine +with a player and a page. English only. + +**The same protocol as the other clients, end to end.** The hub signs it in, +issues the per-node token (E10) and relays the WebRTC offer and answer; from the +DataChannel on, everything goes to the node and is sealed under the group key +(§5.1, §5.2), and transfers take leases like any client's (§5.5). Nothing +data-related passes through the hub. + +**Identity on a node, in this order:** the identity this machine kept from an +earlier run; the one waiting for a device approval; the bundle the node keeps +for browsers, opened with the passphrase (§3.7) and then kept locally; failing +all three, a new key pair filed as a device request (§3.3) — the code is +printed, an already linked device approves it, and the client joins with its +own keys. It never writes a bundle. That is the desktop client's position: an +installed program, no hub-served code (T3 does not apply), so device linking +is admitted for it as for the application. + +**What it keeps.** `state.json` (hub, account, pinned node keys) and +`secrets.enc` (AES-GCM: the refresh token, the device hub key, the node +identities) in the user's data directory (`$XDG_DATA_HOME/meshbay-cli`, +`%LOCALAPPDATA%\meshbay-cli`). The key that opens `secrets.enc` is in the OS +key storage; where there is none, in a 0600 file, which `login` says once. The +passphrase is never stored: a remembered session renews with the refresh +token, else with the device hub key, and asks for the passphrase only when both +fail. Two runs renewing at once take a file lock, so a rotated refresh token is +never spent twice. + +**Node keys are pinned at first contact.** A node presenting another key is +refused before anything is sent (exit 6) until `meshbay-cli trust reset NODE` +— the deliberate way out, for a rotation the operator announced. + +**Text from the network is data.** Every name a node or a member chose is +printed with control and bidi characters escaped, and drawn as plain text, +never markup, in the interactive view. Local names are made writable on the +machine and kept inside the destination; on Windows a download carries the +Mark of the Web. + +**Remote paths** are `group[@owner]/root/folder/file`: `@owner` only when two +groups share a name, names compared without case, `%2F` and `%25` for a `/` or +a `%` in a name, and a link copied from the application accepted as it is. + +**Concurrency.** At most six WebRTC negotiations in flight, one connection per +group; a group whose node is away is reported without stopping the others +(exit 4). + +**Exit codes** are a contract for scripts: 1 usage, 2 sign-in, 3 not found, +4 partial, 5 node unreachable or waiting for device approval, 6 node key +changed, 7 version, 8 refused before anything was sent, 130 interrupted. + +**Packaging.** Its own `.deb`/`.rpm` (`meshbay-cli`, with the packages only it +uses, on top of `meshbay-common`'s venv) and a man page generated from its +argument parser; on Windows, `meshbay-cli.exe` beside `meshbay-node.exe` in +the Full installer and the Store package, not in Light. + ## 9. Group applications ### 9.1 The plug-in architecture @@ -4487,7 +4556,9 @@ phone's photo backup (§9.12), the operator CLI and loopback control API, the desktop client through its identity and download stages, account recovery, the Windows port through packaging, and the Android client — the shell, native keys, downloads and uploads, casting, -and notifications while it is closed, fetched or pushed (§11.3). +and notifications while it is closed, fetched or pushed (§11.3) — and the +command-line client: groups, listing, downloads and uploads with resume, +device linking, and an interactive two-pane view (§8.6). The packages install: a machine has been taken from the built artefacts to a running hub and node on **Ubuntu 26.04 (`.deb`), Fedora 44 (`.rpm`) and @@ -4518,6 +4589,7 @@ process runs it — `systemctl --user` on Linux, Task Scheduler on Windows. | — | **HEIC/HEIF in Photos.** The indexer does not classify `.heic`/`.heif` as images and no browser but Safari draws them, so a phone that shoots HEIC has its backed-up photos stored and listed in Files but absent from Photos. Needs `pillow-heif` in every package and a JPEG rendition for the viewer (§9.12) | | — | **Photo backup with the application closed**, and **"remove what this phone sent"** (§9.12). A closed application has no page and so no transport; it runs at the next opening | | — | **Android: phone behaviour and release** — the back button driving the page, recovery from a network handover, keeping a download alive with the screen off, lock-screen media controls, and an update channel (§11.3) | +| — | **Command-line client: cross-group search and chat** (§8.6). Not run on Windows yet; its transfer windows are not yet measured against a node on another machine | | — | **Federation between two hubs.** The protocol is written and switched off in the code (§7.6); what is not built is one run between two machines | ### 15.3 Open, and why each is where it is @@ -4592,7 +4664,7 @@ superseded document kept under `docs/`, a note somebody holds elsewhere. | `draft-v5 §6.2`–`§6.4` — node registration, signaling, client addresses | §7.2 | | `draft-v5 §7` — cryptography | §4.1–§4.4, §4.6 | | `draft-v5 §7.1` — the keypair bundle and C4 | §3.7 | -| `draft-v5 §8.1`, `§8.2`, `desktop-client-v1.md §2` — the two clients, the shell | §8.1, §8.2 | +| `draft-v5 §8.1`, `§8.2`, `desktop-client-v1.md §2` — the clients, the shell | §8.1, §8.2 | | `draft-v5 §9`, `draft-v6 §5` — open items | §15.3 | | `draft-v5 §10` — testing posture | §12 | | `draft-v6 §2.2`, `desktop-client-v1.md §4`, `§4.5`, `§4.6` — device linking | §3.3 | -- cgit v1.2.3