fix(cli): no base64 fallback on direct-message decrypt failure
The push handler previously fell through to base64-decoding the raw ciphertext whenever decryptDirect() returned null. For direct (crypto_box) messages that produces garbage binary which surfaces as garbled bytes in Claude's <channel> reminder. Limit the base64 fallback to legacy broadcast/channel messages (no senderPubkey), and emit a clearer "⚠ message from <pubkey> failed to decrypt" warning when direct decryption fails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,8 +73,13 @@ function resolveClient(to: string): {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decryptFailedWarning(senderPubkey: string): string {
|
||||||
|
const who = senderPubkey ? senderPubkey.slice(0, 12) + "…" : "unknown sender";
|
||||||
|
return `⚠ message from ${who} failed to decrypt (tampered or wrong keypair)`;
|
||||||
|
}
|
||||||
|
|
||||||
function formatPush(p: InboundPush, meshSlug: string): string {
|
function formatPush(p: InboundPush, meshSlug: string): string {
|
||||||
const body = p.plaintext ?? "(decryption failed)";
|
const body = p.plaintext ?? decryptFailedWarning(p.senderPubkey);
|
||||||
return `[${meshSlug}] from ${p.senderPubkey.slice(0, 12)}… (${p.priority}, ${p.createdAt}):\n${body}`;
|
return `[${meshSlug}] from ${p.senderPubkey.slice(0, 12)}… (${p.priority}, ${p.createdAt}):\n${body}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +87,7 @@ export async function startMcpServer(): Promise<void> {
|
|||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
|
||||||
const server = new Server(
|
const server = new Server(
|
||||||
{ name: "claudemesh", version: "0.1.1" },
|
{ name: "claudemesh", version: "0.1.2" },
|
||||||
{
|
{
|
||||||
capabilities: {
|
capabilities: {
|
||||||
experimental: { "claude/channel": {} },
|
experimental: { "claude/channel": {} },
|
||||||
@@ -215,7 +220,7 @@ If you have multiple joined meshes, prefix the \`to\` argument of send_message w
|
|||||||
const fromName = fromPubkey
|
const fromName = fromPubkey
|
||||||
? `peer-${fromPubkey.slice(0, 8)}`
|
? `peer-${fromPubkey.slice(0, 8)}`
|
||||||
: "unknown";
|
: "unknown";
|
||||||
const content = msg.plaintext ?? "(decryption failed)";
|
const content = msg.plaintext ?? decryptFailedWarning(fromPubkey);
|
||||||
try {
|
try {
|
||||||
await server.notification({
|
await server.notification({
|
||||||
method: "notifications/claude/channel",
|
method: "notifications/claude/channel",
|
||||||
|
|||||||
@@ -312,10 +312,14 @@ export class BrokerClient {
|
|||||||
this.mesh.secretKey,
|
this.mesh.secretKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// If decryption failed, fall back to base64 UTF-8 unwrap —
|
// Legacy/broadcast path: no senderPubkey means the message
|
||||||
// this covers the legacy plaintext path for broadcasts/channels
|
// was not crypto_box'd, so base64 UTF-8 unwrap is correct.
|
||||||
// until channel crypto lands.
|
// For direct messages (senderPubkey present) we MUST NOT
|
||||||
if (plaintext === null && ciphertext) {
|
// base64-decode the ciphertext on decrypt failure — that
|
||||||
|
// produces garbage binary that surfaces as garbled bytes
|
||||||
|
// to Claude. Leave plaintext=null and let consumers emit
|
||||||
|
// a clear "failed to decrypt" warning.
|
||||||
|
if (plaintext === null && ciphertext && !senderPubkey) {
|
||||||
try {
|
try {
|
||||||
plaintext = Buffer.from(ciphertext, "base64").toString("utf-8");
|
plaintext = Buffer.from(ciphertext, "base64").toString("utf-8");
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user