From 656c238295559222cfe7708935fa2fd84d6cf45d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 05:44:15 -0400 Subject: [PATCH] fix(telegram): ignore unsafe cached message ids --- extensions/telegram/src/message-cache.test.ts | 24 ++++++++++++++++++ extensions/telegram/src/message-cache.ts | 25 +++++++++++-------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/extensions/telegram/src/message-cache.test.ts b/extensions/telegram/src/message-cache.test.ts index 9650ff1c8d9..e1612a17ed8 100644 --- a/extensions/telegram/src/message-cache.test.ts +++ b/extensions/telegram/src/message-cache.test.ts @@ -405,6 +405,30 @@ describe("telegram message cache", () => { expect(recent.map((entry) => entry.messageId)).toEqual(["9122", "9123", "9124"]); }); + it("does not use unsafe message ids as recent-before cutoffs", async () => { + const cache = createTelegramMessageCache(); + await cache.record({ + accountId: "default", + chatId: 7, + msg: { + chat: { id: 7, type: "private", first_name: "Nora" }, + message_id: 9124, + date: 1736380700, + text: "State message", + from: { id: 1, is_bot: false, first_name: "Nora" }, + } as Message, + }); + + const recent = await cache.recentBefore({ + accountId: "default", + chatId: 7, + messageId: "9007199254740992", + limit: 10, + }); + + expect(recent).toEqual([]); + }); + it("reads legacy sidecar records as a persistent-store fallback", async () => { const storePath = `/tmp/openclaw-telegram-message-cache-state-migrate-${process.pid}-${Date.now()}.json`; const persistedPath = resolveTelegramMessageCachePath(storePath); diff --git a/extensions/telegram/src/message-cache.ts b/extensions/telegram/src/message-cache.ts index 0c0be4c297e..1bba21603df 100644 --- a/extensions/telegram/src/message-cache.ts +++ b/extensions/telegram/src/message-cache.ts @@ -2,6 +2,7 @@ import { createHash } from "node:crypto"; import fs from "node:fs"; import type { Message } from "grammy/types"; import { formatLocationText } from "openclaw/plugin-sdk/channel-inbound"; +import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime"; import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime"; import { logVerbose } from "openclaw/plugin-sdk/runtime-env"; import { appendRegularFileSync, replaceFileAtomicSync } from "openclaw/plugin-sdk/security-runtime"; @@ -240,6 +241,10 @@ function readOptionalString(record: Record, key: string): strin return isString(value) ? value : undefined; } +function parseSafeMessageId(value: string | undefined): number | undefined { + return value === undefined ? undefined : parseStrictPositiveInteger(value); +} + function isTelegramSourceMessage(value: unknown): value is Message { return ( isRecord(value) && @@ -788,14 +793,14 @@ export function createTelegramMessageCache(params?: { if (!messageId || limit <= 0) { return []; } - const targetId = Number(messageId); - if (!Number.isFinite(targetId)) { + const targetId = parseSafeMessageId(messageId); + if (targetId === undefined) { return []; } return (await listChatMessages({ accountId, chatId, threadId })) .filter((entry) => { - const entryId = Number(entry.messageId); - return Number.isFinite(entryId) && entryId < targetId; + const entryId = parseSafeMessageId(entry.messageId); + return entryId !== undefined && entryId < targetId; }) .slice(-limit); }, @@ -820,9 +825,9 @@ function compareCachedMessageNodes( left: TelegramCachedMessageNode, right: TelegramCachedMessageNode, ) { - const leftId = Number(left.messageId); - const rightId = Number(right.messageId); - if (Number.isFinite(leftId) && Number.isFinite(rightId)) { + const leftId = parseSafeMessageId(left.messageId); + const rightId = parseSafeMessageId(right.messageId); + if (leftId !== undefined && rightId !== undefined) { return leftId - rightId; } return (left.messageId ?? "").localeCompare(right.messageId ?? ""); @@ -845,9 +850,9 @@ function isAfterSessionBoundary( if (!boundary) { return true; } - const nodeId = Number(node.messageId); - const boundaryId = Number(boundary.messageId); - if (Number.isFinite(nodeId) && Number.isFinite(boundaryId)) { + const nodeId = parseSafeMessageId(node.messageId); + const boundaryId = parseSafeMessageId(boundary.messageId); + if (nodeId !== undefined && boundaryId !== undefined) { return nodeId > boundaryId; } if (