diff --git a/CHANGELOG.md b/CHANGELOG.md index 16e192a1b8b..90e77e2dde1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ Docs: https://docs.openclaw.ai - BlueBubbles/inbound: add a persistent file-backed GUID dedupe so MessagePoller webhook replays after BB Server restart or reconnect no longer cause the agent to re-reply to already-handled messages. (#19176, #12053, #66816) Thanks @omarshahine. - Secrets/plugins/status: align SecretRef inspect-vs-strict handling across plugin preload, read-only status/agents surfaces, and runtime auth paths so unresolved refs no longer crash read-only CLI flows while runtime-required non-env refs stay strict. (#66818) Thanks @joshavant. - Memory/dreaming: stop ordinary transcripts that merely quote the dream-diary prompt from being classified as internal dreaming runs and silently dropped from session recall ingestion. (#66852) Thanks @gumadeiras. -- Telegram/documents: sanitize binary reply and archive-extraction paths so `.epub` and `.mobi` uploads can no longer leak raw binary into prompt context through reply metadata or `text/plain` coercion. (#66877) Thanks @martinfrancois. +- Telegram/documents: sanitize binary reply context and ZIP-like archive extraction so `.epub` and `.mobi` uploads can no longer leak raw binary into prompt context through reply metadata or archive-to-`text/plain` coercion. (#66877) Thanks @martinfrancois. ## 2026.4.14 diff --git a/extensions/telegram/src/bot-message-context.session.ts b/extensions/telegram/src/bot-message-context.session.ts index 23491d253ab..6cf041b643b 100644 --- a/extensions/telegram/src/bot-message-context.session.ts +++ b/extensions/telegram/src/bot-message-context.session.ts @@ -221,14 +221,29 @@ export async function buildTelegramInboundContextPayload(params: { : "" }]\n` : ""; + const buildReplySupplementalLines = (params: { body?: string }) => { + const lines: string[] = []; + const forwardAnnotation = replyForwardAnnotation.trimEnd(); + if (forwardAnnotation) { + lines.push(forwardAnnotation); + } + if (params.body) { + lines.push(params.body); + } + return lines.length > 0 ? `\n${lines.join("\n")}` : ""; + }; const replySuffix = visibleReplyTarget ? visibleReplyTarget.kind === "quote" ? `\n\n[Quoting ${visibleReplyTarget.sender}${ visibleReplyTarget.id ? ` id:${visibleReplyTarget.id}` : "" - }]\n${replyForwardAnnotation}"${visibleReplyTarget.body}"\n[/Quoting]` + }]${buildReplySupplementalLines({ + body: visibleReplyTarget.body ? `"${visibleReplyTarget.body}"` : undefined, + })}\n[/Quoting]` : `\n\n[Replying to ${visibleReplyTarget.sender}${ visibleReplyTarget.id ? ` id:${visibleReplyTarget.id}` : "" - }]\n${replyForwardAnnotation}${visibleReplyTarget.body}\n[/Replying]` + }]${buildReplySupplementalLines({ + body: visibleReplyTarget.body, + })}\n[/Replying]` : ""; const forwardPrefix = visibleForwardOrigin ? `[Forwarded from ${visibleForwardOrigin.from}${ diff --git a/extensions/telegram/src/bot.test.ts b/extensions/telegram/src/bot.test.ts index 9e70db4e287..86a06f3a861 100644 --- a/extensions/telegram/src/bot.test.ts +++ b/extensions/telegram/src/bot.test.ts @@ -1280,7 +1280,7 @@ describe("createTelegramBot", () => { expect(payload.ReplyToSender).toBe("Ada"); }); - it("filters binary reply captions from ReplyToBody", async () => { + it("keeps reply linkage while omitting filtered binary reply captions", async () => { onSpy.mockClear(); sendMessageSpy.mockClear(); replySpy.mockClear(); @@ -1307,7 +1307,8 @@ describe("createTelegramBot", () => { const payload = replySpy.mock.calls[0][0]; expect(payload.Body).toContain("[Replying to Ada id:9001]"); expect(payload.Body).not.toContain("PK"); - expect(payload.ReplyToBody).toBe("[unsafe reply text omitted]"); + expect(payload.Body).not.toContain("unsafe reply text omitted"); + expect(payload.ReplyToBody).toBeUndefined(); expect(payload.ReplyToId).toBe("9001"); expect(payload.ReplyToSender).toBe("Ada"); }); diff --git a/extensions/telegram/src/bot/helpers.test.ts b/extensions/telegram/src/bot/helpers.test.ts index 5c12c14c902..3c8f8aefb72 100644 --- a/extensions/telegram/src/bot/helpers.test.ts +++ b/extensions/telegram/src/bot/helpers.test.ts @@ -361,7 +361,7 @@ describe("describeReplyTarget", () => { } as any); expect(result?.id).toBe("1"); expect(result?.sender).toBe("Alice"); - expect(result?.body).toBe("[unsafe reply text omitted]"); + expect(result?.body).toBeUndefined(); }); it("falls back to reply text when quote text is binary", () => { diff --git a/extensions/telegram/src/bot/helpers.ts b/extensions/telegram/src/bot/helpers.ts index 95dabb7b789..70da79d41b3 100644 --- a/extensions/telegram/src/bot/helpers.ts +++ b/extensions/telegram/src/bot/helpers.ts @@ -40,7 +40,6 @@ export { }; const TELEGRAM_GENERAL_TOPIC_ID = 1; -const FILTERED_TELEGRAM_REPLY_TEXT = "[unsafe reply text omitted]"; function hadUnsafeTelegramText(raw: unknown, sanitized: string): boolean { return typeof raw === "string" && raw.trim().length > 0 && sanitized.trim().length === 0; @@ -336,7 +335,7 @@ export type TelegramReplyTarget = { sender: string; senderId?: string; senderUsername?: string; - body: string; + body?: string; kind: "reply" | "quote"; /** Forward context if the reply target was itself a forwarded message (issue #9619). */ forwardedFrom?: TelegramForwardedContext; @@ -397,7 +396,7 @@ export function describeReplyTarget(msg: Message): TelegramReplyTarget | null { sender: senderLabel, senderId: replyLike?.from?.id != null ? String(replyLike.from.id) : undefined, senderUsername: replyLike?.from?.username ?? undefined, - body: body || FILTERED_TELEGRAM_REPLY_TEXT, + body: body || undefined, kind, forwardedFrom, };