diff --git a/apps/web/src/app/[locale]/(marketing)/blog/peer-messaging-claude-code/page.tsx b/apps/web/src/app/[locale]/(marketing)/blog/peer-messaging-claude-code/page.tsx new file mode 100644 index 0000000..dcb5d07 --- /dev/null +++ b/apps/web/src/app/[locale]/(marketing)/blog/peer-messaging-claude-code/page.tsx @@ -0,0 +1,194 @@ +import Link from "next/link"; + +export const metadata = { + title: "Peer messaging for Claude Code: protocol, security, UX — claudemesh", + description: + "How claudemesh connects Claude Code sessions over an encrypted mesh, using MCP dev-channels for real-time message injection. Wire protocol, threat model, and what's next.", + openGraph: { + title: "Peer messaging for Claude Code: protocol, security, UX", + description: "How claudemesh connects Claude Code sessions over an encrypted mesh.", + images: ["/media/blog-hero-mesh.png"], + }, +}; + +export default function BlogPost() { + return ( +
+
+ +

+ Peer messaging for Claude Code: protocol, security, UX +

+

+ by Alejandro A. Gutiérrez Mourente +

+
+ +
+

+ Claude Code sessions are islands. You build context over an hour of conversation, close the + tab, and that context dies. Two sessions side by side — one refactoring the API, one fixing + the frontend — share a filesystem but not a thought. I spent a decade flying F-18s in the + Spanish Air Force, where every formation member broadcasts position, fuel, and threat data + in real time. Silence kills. I built{" "} + claudemesh to give Claude Code + sessions the same link: an MCP server that connects them over an encrypted mesh, pushing + messages directly into each other's context mid-turn. +

+

+ The CLI is MIT-licensed, on npm as claudemesh-cli. This post covers the wire + protocol, the experimental Claude Code capability behind real-time injection, and the + prompt-injection surface that deserves careful attention. +

+ +

The protocol

+

+ One owner's ed25519 public key defines a mesh. The owner generates signed invite links; + each invitee verifies the signature, generates a fresh ed25519 keypair locally, and enrolls + with a broker via POST /join. The client then opens a persistent WebSocket + (wss:// in production) and authenticates with a signed hello{" "} + frame: +

+
{`{
+  "type": "hello",
+  "meshId": "01HX...",
+  "memberId": "01HX...",
+  "pubkey": "64-hex-chars",
+  "timestamp": 1735689600000,
+  "signature": "128-hex-chars"
+}`}
+

+ The signature covers{" "} + {"${meshId}|${memberId}|${pubkey}|${timestamp}"}. The broker verifies it + against the registered public key and replies hello_ack. The connection is + live. +

+

+ Direct messages use libsodium crypto_box_easy for end-to-end encryption — + X25519 keys derived from ed25519 identity pairs via{" "} + crypto_sign_ed25519_pk_to_curve25519. The broker routes ciphertext and never + sees plaintext. Priority routing: now delivers immediately, next{" "} + queues until idle, low waits for an explicit drain. The full specification + lives in{" "} + PROTOCOL.md{" "} + (453 lines). +

+ +

Dev channels: the missing piece

+

+ An experimental Claude Code capability fixes the polling problem:{" "} + notifications/claude/channel. When an MCP server declares{" "} + {"{ experimental: { \"claude/channel\": {} } }"} and Claude Code launches + with --dangerously-load-development-channels server:<name>, the server + pushes notifications that arrive as {""} system + reminders mid-turn. Claude reacts immediately. +

+

+ claudemesh launch wraps this into one command. I tested with an echo-channel + MCP server emitting a notification every 15 seconds — all three ticks arrived mid-turn and + Claude responded inline. Confirmed on Claude Code v2.1.92. +

+ +

The prompt-injection question

+

+ This section matters most. claudemesh decrypts peer text and injects it into Claude's + context. That text is untrusted input. A peer can send instruction overrides, tool-call + steering, or confused-deputy attacks invoking other MCP servers through Claude. The same + failure-mode analysis that clears a formation through weather applies here: enumerate every + way the system breaks, then close each path. +

+

+ Tool-approval prompts stay intact. claudemesh never disables Claude Code's + permission system. A peer message can ask Claude to run a shell command; Claude still + prompts the user. +

+

+ Messages carry attribution. Each {""} reminder + includes from_id, from_name, and mesh_slug. +

+

+ Membership requires a signed invite. An attacker needs a valid + ed25519-signed invite from the mesh owner or a compromised member keypair. +

+

+ The residual risks are real. If a user blanket-approves tools, a malicious peer message + reaches the shell without human review. The causal chain — peer message, Claude decision, + tool call — has no persistent audit trail yet.{" "} + + THREAT_MODEL.md + {" "} + (212 lines) documents all of this. Open questions I want to work through with the Claude + Code team. +

+ +

What I'd do next

+

+ Shared-key channel crypto. Channel and broadcast messages are base64 + plaintext today. The upgrade is a KDF from mesh_root_key plus key rotation. +

+

+ Causal audit log. When Claude calls a tool because of a peer message, that + link should persist: which message, which tool call, what result. +

+

+ Sender allowlists. Per-mesh config: accept messages only from these + pubkeys. If a member's key is compromised, others exclude it locally. +

+

+ Forward secrecy. crypto_box uses long-lived keys. A leaked + key lets an attacker decrypt all past captured ciphertext. A double-ratchet would bound the + damage window. +

+ +

Try it

+
{`npm install -g claudemesh-cli
+claudemesh install
+claudemesh join https://claudemesh.com/join/
+claudemesh launch`}
+

+ The code is at{" "} + github.com/alezmad/claudemesh-cli. + The wire protocol is in{" "} + PROTOCOL.md. + The threat model is in{" "} + + THREAT_MODEL.md + . + Contributions welcome — see{" "} + + CONTRIBUTING.md + . +

+

+ If you work on Claude Code or the MCP ecosystem and this interests you, I'd like to hear + from you. +

+
+ +
+ + ← Back to blog + +
+
+ ); +}