Files
claudemesh/.artifacts/specs/2026-04-15-invite-v2-cli-migration.md
Alejandro Gutiérrez 45d85f5eaa
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled
chore: wrap up the gap-closing session
- info/inbox commands → unified render.ts
- install route: drop in-memory counter, rely on PostHog + structured logs
- docs: roadmap, CLAUDE.md reflect alpha.31 state
- tests workflow now also builds + smoke-tests the CLI bundle
- homebrew tap bootstrap kit in packaging/homebrew-tap-bootstrap/
  (README + copy of the formula template for dropping into the tap repo)
- upstream Claude Code issue draft for rich <channel> UI

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

3.6 KiB

Invite v2 — CLI migration (server-side already shipped)

Current state

Server-side (broker) — DEPLOYED

  • canonicalInviteV2 bytes format (crypto.ts)
  • verifyInviteV2 signature check
  • claimInviteV2Core at POST /invites/:code/claim
  • sealRootKeyToRecipient using crypto_box_seal
  • Every v1 invite also stores capability_v2 for cross-compat
  • Web route /api/public/invites/:code/claim proxies to broker

Client-side (CLI) — NOT MIGRATED The CLI still uses the v1 flow (enrollWithBroker) which reads mesh_root_key from the invite token's base64 payload. This means:

  • Long URL /join/<token> contains the root key
  • Short URL /i/<code> resolves to the long URL → still contains root key
  • Anyone who can read the URL (history, screenshot, mail archive) has the key

The v2 CLI flow

parseInviteLinkV2(url)
  → short URL /i/<code>? GET /api/public/invite-code/:code
    → returns `{ found, code, mesh_slug, broker_url, owner_pubkey,
                 canonical_v2, expires_at, role }`  (NO root_key)
  → generate local x25519 keypair (curve25519)
  → POST /invites/<code>/claim { recipient_x25519_pubkey, display_name }
    → broker verifies capability_v2 signature
    → broker seals mesh.root_key with crypto_box_seal(root_key, our_pubkey)
    → returns { sealed_root_key, mesh_id, member_id, owner_pubkey, canonical_v2 }
  → open sealed_root_key with our x25519 secret key
  → store root_key in ~/.claudemesh/config.json.meshes[].rootKey
    (NOT in the invite link — it was never transmitted unsealed)
  → upgrade enroll to use claim response instead of the /join endpoint

What needs to change in the CLI

  1. New file apps/cli/src/services/invite/parse-v2.ts

    • Detect short URL, resolve via /api/public/invite-code/:code
    • Expect the API returns v2 shape (server already has this route; verify field names)
    • Generate x25519 keypair via libsodium
    • POST to claim endpoint
    • Unseal root_key
  2. Conditional in parseInviteLink

    • If URL is short-form and broker supports v2, use the new path
    • Fall back to v1 for legacy long-form URLs in transit
  3. Config schema already has rootKey per mesh — just write from unsealed bytes instead of from the token payload.

  4. Spec test tests/golden/invite-v2.test.ts

    • Broker already has claimInviteV2Core tests; add a CLI-side end-to-end that hits a local broker and verifies the sealed key round-trips.

Why it wasn't rushed in this session

Crypto code deserves review. The server-side v2 shipped weeks ago with its own testing and audit; the CLI migration needs the same rigor — at minimum, a test that proves the sealed key we unseal matches the root_key the broker had in its DB, verified against canonical_v2 signature.

The current v1 flow is a known quantity (the root_key-in-URL risk is documented in the spec). Broker is already v2-ready so when the CLI migration lands, emails / links can immediately start using the claim-only short URL without a server deploy.

Rollout plan

  1. Ship CLI v2 path behind CLAUDEMESH_INVITE_V2=1 env.
  2. Dogfood: new invites generated by claudemesh share use /api/public/invite-code/:code with v2-shape response that omits token; CLI resolves via claim.
  3. Verify with claudemesh verify safety numbers cross-check.
  4. After 2 weeks uneventful, flip default to v2.
  5. After 60 days, stop embedding root_key in long URLs entirely.
  6. v3 (future): short URL becomes the only form.

Effort

~1 day of focused crypto + testing. Broker work is done; API work is done; CLI work is a new parse path + a new enroll path + a few tests.