diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-02 11:54:56 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-02 11:54:56 +0200 |
| commit | d7b7f1049d95e45e6316ac419cb434088c15bd5e (patch) | |
| tree | 77da46e2fe3cb70af5fb2eebcbb1d69dad410b88 /packages/meshbay-common/src | |
| parent | 56a8cf9167e8c7b0f2df15afed88031608adf782 (diff) | |
| parent | 754387590fa1754436b4648f969915888c6f6c9e (diff) | |
| download | meshbay-d7b7f1049d95e45e6316ac419cb434088c15bd5e.tar.gz | |
Diffstat (limited to 'packages/meshbay-common/src')
4 files changed, 30 insertions, 64 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/__init__.py b/packages/meshbay-common/src/meshbay_common/__init__.py index b68e828..2a58326 100644 --- a/packages/meshbay-common/src/meshbay_common/__init__.py +++ b/packages/meshbay-common/src/meshbay_common/__init__.py @@ -260,5 +260,17 @@ __version__ = "0.17.0" # a refusal, across the break. The break is confined to them, so the floor stays # at 4.0: everything else a 4.x peer does still works, and nothing is left # unsigned on either side — no node accepts the old subjects. -MNP_VERSION = "5.0" +# +# 6.0 (2026-10-02) is a MAJOR — three signed operations are removed: `root_add`, +# `root_update` and `group_attach`. What of the operator's disk is shared, and +# whether members may write there, is decided on the node's own machine (the +# desktop application over loopback, or the CLI), never +# over the wire. A 5.x client that sends one gets no answer, as for any unknown +# type; everything else it does still works, so the floor stays at 4.0. +# The same version removes ten operator messages no client ever sent — +# `gek_rotate`, `member_unpin`, `transfer_limits`, `group_detach` (signed) and +# `node_status`, `node_settings_set`, `roster_read`, `denylist_read`, +# `denylist_clear`, `node_reload` — whose work the Node page and the CLI do over +# loopback. +MNP_VERSION = "6.0" MHP_VERSION = "0.1" diff --git a/packages/meshbay-common/src/meshbay_common/adminop.py b/packages/meshbay-common/src/meshbay_common/adminop.py index 4379519..bd7a439 100644 --- a/packages/meshbay-common/src/meshbay_common/adminop.py +++ b/packages/meshbay-common/src/meshbay_common/adminop.py @@ -47,14 +47,6 @@ OP_INVITE_LINK_CREATE = "invite_link_create" # Taking back an unredeemed link, by the handle it was issued with. OP_INVITE_CANCEL = "invite_cancel" OP_MEMBER_REVOKE = "member_revoke" -# Rotating the group key is what actually takes it away from a revoked member: -# revocation stops the node serving the *next* key, and they still hold the -# current one. The node generates the new key itself with its own CSPRNG, so -# nothing arriving over MNP contributes key material — the C5b rule is about -# key material from outside, not about the instruction. -OP_GEK_ROTATE = "gek_rotate" -# Forgetting a pinned identity, so someone can pair again after losing a device. -OP_MEMBER_UNPIN = "member_unpin" # Which group "applications" (Chat, Files, and whatever registers later) are # shown to members. Signed like the rest: it decides what a member sees, not # anything about key material, but an unsigned toggle would let any member @@ -67,11 +59,6 @@ OP_APPS_ENABLED = "apps_enabled" # security property in itself, but the pattern (every operator setting is # signed) is what keeps the authorization model simple to reason about. OP_SET_SCAN_SETTINGS = "set_scan_settings" -# How many transfers one member may run at once in this group. Signed like the -# rest: an unsigned cap is one any member can raise for themselves, which makes -# the control a suggestion. The subject is "d=2,u=2" so what the operator is -# shown before signing names the outcome and not the operation. -OP_TRANSFER_LIMITS = "transfer_limits" # Whether the node uses the operator's own API token/language instead of the # shipped default — node-wide (docs/MESHBAY_DESIGN.md §9.7), one credential # shared by every group. Signed like the rest: it turns on outbound @@ -101,7 +88,6 @@ OP_TMDB_REMATCH = "tmdb_rematch" # the lesson was already learned once). Signed for the same reason as # tmdb_enabled. OP_MUSICBRAINZ_ENABLED = "musicbrainz_enabled" -OP_ROOT_ADD = "root_add" OP_ROOT_REMOVE = "root_remove" # One op for every application's directories. The subject is # "<app>:<comma-joined sorted paths>" so what the operator is shown before @@ -115,18 +101,26 @@ OP_CHAT_LINK_PREVIEW = "chat_link_preview" # operator's, signed like the other per-group switches. OP_SEARCH_LISTED = "search_listed" # Open a new chat epoch for a group, by hand. The removals that matter open one -# by themselves (member revoke/unpin, device revoke, gek_rotate); this is the -# operator saying "do it anyway", which is the same shape as `gek_rotate` and -# signed for the same reason. +# by themselves (member revoke/unpin, device revoke, group key rotation); this is the +# operator saying "do it anyway", and is signed like the rest. # # There is no op for *enabling* chat encryption. It is not a setting — MNP 2.0 # has no plaintext chat to fall back to. OP_CHAT_EPOCH = "chat_epoch" -OP_ROOT_UPDATE = "root_update" OP_ROOT_EJECT = "root_eject" OP_ROOT_PLUG = "root_plug" -OP_GROUP_ATTACH = "group_attach" -OP_GROUP_DETACH = "group_detach" +# Also gone with 6.0, because no client ever sent them: `gek_rotate`, +# `member_unpin`, `transfer_limits` and `group_detach`. Rotating the group key, +# forgetting an identity, the per-member transfer caps and no longer hosting a +# group are done on the node's machine — the desktop application's Node page +# or the CLI. A door nobody uses is an untested way in. +# OP_ROOT_ADD, OP_ROOT_UPDATE and OP_GROUP_ATTACH are gone (MNP 6.0). Each one +# chose what of the operator's disk is shared and who may write there, and a +# signature proves only that the operator's key signed — in a browser, through +# code the hub serves; on the desktop, through a renderer that parses content +# from nodes. Sharing a folder, hosting a group and opening a folder to writes +# are done on the node's own machine (loopback) or with +# the CLI, and a message that does not exist cannot be mis-authorized. # OP_GEK_BUNDLE_STORE is gone. Members no longer hand the node key material at # all: the node holds the GEK and wraps it itself, for a key the recipient proved # they hold (see `join.py` and docs/MESHBAY_DESIGN.md §3.4). The operation existed @@ -159,17 +153,6 @@ def secret_digest(value: str | None) -> str | None: return "sha256:" + hashlib.sha256(value.encode()).hexdigest() -def root_add_subject(path: str, name: str, kind: str, writable: bool, - removable: bool) -> str: - return structured_subject({"path": path, "name": name, "kind": kind, - "writable": writable, "removable": removable}) - - -def group_attach_subject(name: str, shared_dir: str, writable: bool) -> str: - return structured_subject({"name": name, "shared_dir": shared_dir, - "writable": writable}) - - def invite_create_subject(user_id: str, username: str) -> str: return structured_subject({"user_id": user_id, "username": username}) diff --git a/packages/meshbay-common/src/meshbay_common/groupbox.py b/packages/meshbay-common/src/meshbay_common/groupbox.py index 260e362..ae79fcc 100644 --- a/packages/meshbay-common/src/meshbay_common/groupbox.py +++ b/packages/meshbay-common/src/meshbay_common/groupbox.py @@ -74,7 +74,7 @@ _INFO = { # the bound that matters is birthday collision under `PURPOSE_UPLOAD` — the only # purpose with real volume, one message per 48 KiB chunk. 2**32 chunks is 200 TB # uploaded under a single GEK before the collision probability reaches 2**-32, -# and `gek_rotate` exists. Deriving the nonce from the payload instead would be +# and the group key can be rotated. Deriving the nonce from the payload instead would be # worse, not better: two chunks of identical bytes are ordinary in a file. NONCE_LEN = 12 # 96-bit, the WebCrypto AES-GCM standard diff --git a/packages/meshbay-common/src/meshbay_common/protocol.py b/packages/meshbay-common/src/meshbay_common/protocol.py index af2d93b..f8e56ec 100644 --- a/packages/meshbay-common/src/meshbay_common/protocol.py +++ b/packages/meshbay-common/src/meshbay_common/protocol.py @@ -154,12 +154,8 @@ class MNP: INVITE_CANCEL = "invite_cancel" # operator → node: take back an unredeemed link MEMBER_REVOKE = "member_revoke" # operator → node: stop serving the key MEMBER_REVOKE_ACK = "member_revoke_ack" - MEMBER_UNPIN = "member_unpin" # operator → node: forget an identity - MEMBER_UNPIN_ACK = "member_unpin_ack" APPS_ENABLED = "apps_enabled" # operator → node: which group apps to show APPS_ENABLED_ACK = "apps_enabled_ack" - TRANSFER_LIMITS = "transfer_limits" # operator → node: per-member caps here - TRANSFER_LIMITS_ACK = "transfer_limits_ack" # node → this group: the new caps SET_SCAN_SETTINGS = "set_scan_settings" # operator → node: reconcile/debounce timing SET_SCAN_SETTINGS_ACK = "set_scan_settings_ack" MEDIA_META_REQ = "media_meta_req" # client → node: TMDB metadata for a path @@ -216,16 +212,8 @@ class MNP: # where it was. DEVICE_HELLO = "device_hello" # device → node: this is me DEVICE_HELLO_ACK = "device_hello_ack" - # Rotation is the half of revocation that revocation cannot do: the node - # generates a fresh key itself, so no key material crosses the wire. - GEK_ROTATE = "gek_rotate" # operator → node: new group key - GEK_ROTATE_ACK = "gek_rotate_ack" INVITE_RESULT = "invite_result" # node → operator: the code, once INVITE_LINK_RESULT = "invite_link_result" # node → operator: link code + handle, once - NODE_STATUS = "node_status" # operator → node: list all groups + roots - NODE_STATUS_ACK = "node_status_ack" # node → operator: full status - ROOT_ADD = "root_add" # operator → node: add a directory to a group - ROOT_ADD_ACK = "root_add_ack" # node → operator: confirmed ROOT_REMOVE = "root_remove" # operator → node: remove a root from a group ROOT_REMOVE_ACK = "root_remove_ack" # node → operator: confirmed # One message for every application's directories, keyed by the app's own @@ -256,34 +244,17 @@ class MNP: # key without having to reconnect. CHAT_EPOCH = "chat_epoch" CHAT_EPOCH_ACK = "chat_epoch_ack" - ROOT_UPDATE = "root_update" # operator → node: a root's flags - ROOT_UPDATE_ACK = "root_update_ack" ROOT_EJECT = "root_eject" # operator → node: mark removable root as ejected ROOT_EJECT_ACK = "root_eject_ack" ROOT_PLUG = "root_plug" # operator → node: re-enable an ejected root ROOT_PLUG_ACK = "root_plug_ack" # Member → node: who is in this group and which device keys they hold, with - # the countersignature that admitted each one. Distinct from ROSTER_READ - # below, which is the operator's view of the whole node: this is scoped to - # one group and answers any member of it, because the point is that a member + # the countersignature that admitted each one. Scoped to one group and + # answers any member of it, because the point is that a member # verifies another member's device *for themselves* rather than trusting the # node's `sender_id` (Tier 2, docs/MESHBAY_DESIGN.md §3.3). GROUP_ROSTER_REQ = "group_roster_req" GROUP_ROSTER_RESP = "group_roster_resp" - ROSTER_READ = "roster_read" # operator → node: identities + members - ROSTER_READ_ACK = "roster_read_ack" - DENYLIST_READ = "denylist_read" # operator → node: show denylist entries - DENYLIST_READ_ACK = "denylist_read_ack" - DENYLIST_CLEAR = "denylist_clear" # operator → node: remove denylist entry(ies) - DENYLIST_CLEAR_ACK = "denylist_clear_ack" - GROUP_ATTACH = "group_attach" # operator → node: host a new group - GROUP_ATTACH_ACK = "group_attach_ack" - GROUP_DETACH = "group_detach" # operator → node: stop hosting a group - GROUP_DETACH_ACK = "group_detach_ack" - NODE_SETTINGS_SET = "node_settings_set" # operator → node: change daemon settings - NODE_SETTINGS_SET_ACK = "node_settings_set_ack" - NODE_RELOAD = "node_reload" # operator → node: re-read node.toml - NODE_RELOAD_ACK = "node_reload_ack" # ── Index entry ─────────────────────────────────────────────────────────────── |