Files
claudemesh/apps/cli
Alejandro Gutiérrez 9d3dbcecaf feat(broker): verify ed25519 hello signature against member pubkey
WS handshake is now authenticated end-to-end. The broker proves that
every connected peer actually holds the secret key for the pubkey
they claim as identity — not just that they know the pubkey.

wire format change:
  {type:"hello", meshId, memberId, pubkey, sessionId, pid, cwd,
   timestamp, signature}
  where signature = ed25519_sign(canonical, secretKey)
  and canonical = `${meshId}|${memberId}|${pubkey}|${timestamp}`

broker verifies on every hello:
1. timestamp within ±60s of broker clock → else close(1008, timestamp_skew)
2. pubkey is 64 hex chars, signature is 128 hex chars → else malformed
3. crypto_sign_verify_detached(signature, canonical, pubkey) → else bad_signature
4. (existing) mesh.member row exists for (meshId, pubkey) → else unauthorized

All rejection paths close the WS with code 1008 + structured error
message + metrics counter increment (connections_rejected_total by
reason).

new modules:
- apps/broker/src/crypto.ts: canonicalHello, verifyHelloSignature,
  HELLO_SKEW_MS constant
- apps/cli/src/crypto/hello-sig.ts: matching signHello helper

clients updated:
- apps/cli/src/ws/client.ts: signs hello before send
- apps/broker/scripts/{peer-a,peer-b}.ts (smoke-test): sign hellos
  with seed-provided secret keys

new regression tests — tests/hello-signature.test.ts (7):
- valid signature accepted
- bad signature (signed with wrong key) rejected
- timestamp too old rejected (>60s)
- timestamp too far in future rejected (>60s)
- tampered canonical field (different meshId at verify time) rejected
- malformed hex pubkey rejected
- malformed signature length rejected

verified live:
- apps/broker/scripts/smoke-test.sh: full hello+ack+send+push flow
- apps/cli/scripts/roundtrip.ts: signed hello + encrypted message
- 55/55 tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:53:40 +01:00
..

@claudemesh/cli

Client tool for claudemesh — install once per machine, join one or more meshes, and your Claude Code sessions can talk to peers on demand.

Install

# From npm (once published)
npm install -g @claudemesh/cli

# Or from the monorepo during dev
cd apps/cli && bun link

Then register the MCP server with Claude Code:

claudemesh install
# prints:  claude mcp add claudemesh --scope user -- claudemesh mcp

Run the printed command, then restart Claude Code.

Join a mesh

claudemesh join ic://join/BASE64URL...

The invite link is generated by whoever runs the mesh. It bundles the mesh id, expiry, signing key, and role. Your CLI verifies it, generates a fresh keypair, enrolls you with the broker, and persists the result to ~/.claudemesh/config.json.

Commands

claudemesh install         # print MCP registration command
claudemesh join <link>     # join a mesh via invite link
claudemesh list            # show joined meshes + identities
claudemesh leave <slug>    # leave a mesh
claudemesh mcp             # start MCP server (stdio — Claude Code only)
claudemesh --help          # show usage

Env overrides

Var Default Purpose
CLAUDEMESH_BROKER_URL wss://ic.claudemesh.com/ws Point at a self-hosted broker
CLAUDEMESH_CONFIG_DIR ~/.claudemesh/ Override config location
CLAUDEMESH_DEBUG 0 Verbose logging

Status

v0.1.0 scaffold — CLI commands + MCP server shell in place. WS broker connection, libsodium crypto, invite-link verification, and auto-install of hooks land in subsequent steps.