diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index 39662138c85..951967c4075 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -1311,6 +1311,42 @@ describe("handleFeishuMessage command authorization", () => { expect(call.Timestamp).toBeLessThanOrEqual(after); }); + it("falls back to Date.now() when create_time is malformed", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + dmPolicy: "open", + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { + sender_id: { + open_id: "ou-attacker", + }, + }, + message: { + message_id: "msg-malformed-create-time", + chat_id: "oc-dm", + chat_type: "p2p", + message_type: "text", + content: JSON.stringify({ text: "hello" }), + create_time: "1700000000000ms", + }, + }; + + const before = Date.now(); + await dispatchMessage({ cfg, event }); + const after = Date.now(); + + const call = mockFinalizeInboundContext.mock.calls.at(0)?.[0] as { Timestamp: number }; + expect(call.Timestamp).toBeGreaterThanOrEqual(before); + expect(call.Timestamp).toBeLessThanOrEqual(after); + }); + it("replies pairing challenge to DM chat_id instead of user:sender id", async () => { const cfg: ClawdbotConfig = { channels: { diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 1b5eef1bf0d..6ca5e65dfb2 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -10,6 +10,7 @@ import { resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute, } from "openclaw/plugin-sdk/conversation-runtime"; +import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime"; import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow, @@ -604,10 +605,9 @@ export async function handleFeishuMessage(params: { // Parse message create_time early so every downstream consumer (pending // history, inbound payload, etc.) uses the original authoring timestamp // instead of the delivery/processing time. Feishu uses a millisecond - // epoch string; fall back to Date.now() only when the field is absent. - const messageCreateTimeMs = event.message.create_time - ? Number.parseInt(event.message.create_time, 10) - : Date.now(); + // epoch string; fall back to Date.now() when absent or malformed. + const messageCreateTimeMs = + parseStrictNonNegativeInteger(event.message.create_time) ?? Date.now(); let requireMention = false; // DMs never require mention; groups may override below if (isGroup) {