Telegram/documents: avoid synthetic reply bodies

This commit is contained in:
Gustavo Madeira Santana
2026-04-14 20:35:48 -04:00
parent 2ee6bb8f2c
commit 5e2c36f483
5 changed files with 24 additions and 9 deletions

View File

@@ -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

View File

@@ -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}${

View File

@@ -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");
});

View File

@@ -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", () => {

View File

@@ -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,
};