diff --git a/apps/cli/package.json b/apps/cli/package.json index e6e577d..d2bb72e 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "claudemesh-cli", - "version": "1.0.0-alpha.33", + "version": "1.0.0-alpha.34", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "keywords": [ "claude-code", diff --git a/apps/cli/src/services/broker/ws-client.ts b/apps/cli/src/services/broker/ws-client.ts index c17bedd..c6dadde 100644 --- a/apps/cli/src/services/broker/ws-client.ts +++ b/apps/cli/src/services/broker/ws-client.ts @@ -1670,11 +1670,17 @@ export class BrokerClient { plaintext = `[${event}]`; } } else if (senderPubkey && nonce && ciphertext) { - plaintext = await decryptDirect( - { nonce, ciphertext }, - senderPubkey, - this.sessionSecretKey ?? this.mesh.secretKey, - ); + // Try the session secret first (per-connection ephemeral key), then + // fall back to the mesh member secret (stable identity). Senders + // may encrypt to either our session pubkey OR our member pubkey + // depending on how the target was resolved; we must match both. + const envelope = { nonce, ciphertext }; + if (this.sessionSecretKey) { + plaintext = await decryptDirect(envelope, senderPubkey, this.sessionSecretKey); + } + if (plaintext === null) { + plaintext = await decryptDirect(envelope, senderPubkey, this.mesh.secretKey); + } } // Legacy/broadcast path: no senderPubkey means the message // was not crypto_box'd, so base64 UTF-8 unwrap is correct.