From 19f22b5924b0917ddd0d4faae95c81c799f7fef7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 30 May 2026 11:41:43 -0400 Subject: [PATCH] fix(feishu): bound approval card expiry --- extensions/feishu/src/bot.card-action.test.ts | 39 +++++++++++++++++++ extensions/feishu/src/card-action.ts | 20 +++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/extensions/feishu/src/bot.card-action.test.ts b/extensions/feishu/src/bot.card-action.test.ts index d44cde6156d..275628e44d1 100644 --- a/extensions/feishu/src/bot.card-action.test.ts +++ b/extensions/feishu/src/bot.card-action.test.ts @@ -270,6 +270,45 @@ describe("Feishu Card Action Handler", () => { expect(handleFeishuMessage).not.toHaveBeenCalled(); }); + it("does not open approval cards when the expiry would exceed a valid Date", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(8_640_000_000_000_000)); + try { + const event: FeishuCardActionEvent = { + operator: { open_id: "u123", user_id: "uid1", union_id: "un1" }, + token: "tok4-boundary", + action: { + value: createFeishuCardInteractionEnvelope({ + k: "meta", + a: FEISHU_APPROVAL_REQUEST_ACTION, + m: { + command: "/new", + prompt: "Start a fresh session?", + }, + c: { + u: "u123", + h: "chat1", + t: "group", + s: "agent:codex:feishu:chat:chat1", + e: 8_640_000_000_000_000, + }, + }), + tag: "button", + }, + context: { open_id: "u123", user_id: "uid1", chat_id: "chat1" }, + }; + + await handleFeishuCardAction({ cfg, event, runtime, accountId: "main" }); + + expect(sendCardFeishuMock).not.toHaveBeenCalled(); + const sendMessage = sendMessageCall(); + expect(sendMessage.to).toBe("chat:chat1"); + expect(String(sendMessage.text)).toContain("payload is invalid"); + } finally { + vi.useRealTimers(); + } + }); + it("runs approval confirmation through the normal message path", async () => { const event = createStructuredQuickActionEvent({ token: "tok5", diff --git a/extensions/feishu/src/card-action.ts b/extensions/feishu/src/card-action.ts index 08a7a376b8f..2c59af5f475 100644 --- a/extensions/feishu/src/card-action.ts +++ b/extensions/feishu/src/card-action.ts @@ -217,6 +217,13 @@ function sanitizeLogValue(v: string): string { return v.replace(/[\r\n]/g, " ").slice(0, 500); } +function resolveFeishuApprovalCardExpiresAt(nowRaw = Date.now()): number | undefined { + const now = asDateTimestampMs(nowRaw); + return now === undefined + ? undefined + : resolveExpiresAtMsFromDurationMs(FEISHU_APPROVAL_CARD_TTL_MS, { nowMs: now }); +} + function cacheResolvedCardActionChatType( cacheKey: string, value: "p2p" | "group", @@ -373,6 +380,17 @@ export async function handleFeishuCardAction(params: { typeof envelope.m?.prompt === "string" && envelope.m.prompt.trim() ? envelope.m.prompt : `Run \`${command}\` in this Feishu conversation?`; + const expiresAt = resolveFeishuApprovalCardExpiresAt(); + if (expiresAt === undefined) { + await sendInvalidInteractionNotice({ + cfg, + event, + reason: "malformed", + accountId, + }); + completeFeishuCardActionToken({ token: event.token, accountId: account.accountId }); + return; + } await sendCardFeishu({ cfg, to: resolveCallbackTarget(event), @@ -382,7 +400,7 @@ export async function handleFeishuCardAction(params: { command, prompt, sessionKey: envelope.c?.s, - expiresAt: Date.now() + FEISHU_APPROVAL_CARD_TTL_MS, + expiresAt, chatType: await resolveCardActionChatType({ event, account,