diff --git a/CHANGELOG.md b/CHANGELOG.md index 042332d3844..c6070c789fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ Docs: https://docs.openclaw.ai - Feishu/actions: expand the runtime action surface with message read/edit, explicit thread replies, pinning, and operator-facing chat/member inspection so Feishu can operate more of the workspace directly. (#47968) Thanks @Takhoffman. - Feishu/topic threads: fetch full thread context, including prior bot replies, when starting a topic-thread session so follow-up turns in Feishu topics keep the right conversation state. (#45254) Thanks @Coobiw. - Feishu/media: keep native image, file, audio, and video/media handling aligned across outbound sends, inbound downloads, thread replies, directory/action aliases, and capability docs so unsupported areas are explicit instead of implied. (#47968) Thanks @Takhoffman. +- Feishu/webhooks: harden signed webhook verification to use constant-time signature comparison and keep malformed short signatures fail-closed in webhook E2E coverage. - WhatsApp/reconnect: restore the append recency filter in the extension inbox monitor and handle protobuf `Long` timestamps correctly, so fresh post-reconnect append messages are processed while stale history sync stays suppressed. (#42588) Thanks @MonkeyLeeT. - WhatsApp/login: wait for pending creds writes before reopening after Baileys `515` pairing restarts in both QR login and `channels login` flows, and keep the restart coverage pinned to the real wrapped error shape plus per-account creds queues. (#27910) Thanks @asyncjason. - Telegram/message send: forward `--force-document` through the `sendPayload` path as well as `sendMedia`, so Telegram payload sends with `channelData` keep uploading images as documents instead of silently falling back to compressed photo sends. (#47119) Thanks @thepagent. diff --git a/extensions/feishu/src/monitor.transport.ts b/extensions/feishu/src/monitor.transport.ts index d619f3cddb3..caab5468378 100644 --- a/extensions/feishu/src/monitor.transport.ts +++ b/extensions/feishu/src/monitor.transport.ts @@ -32,6 +32,15 @@ function isFeishuWebhookPayload(value: unknown): value is Record, @@ -63,7 +72,7 @@ function isFeishuWebhookSignatureValid(params: { .createHash("sha256") .update(timestamp + nonce + encryptKey + JSON.stringify(params.payload)) .digest("hex"); - return computedSignature === signature; + return timingSafeEqualString(computedSignature, signature); } function respondText(res: http.ServerResponse, statusCode: number, body: string): void { diff --git a/extensions/feishu/src/monitor.webhook-e2e.test.ts b/extensions/feishu/src/monitor.webhook-e2e.test.ts index a11957e3393..33035a735f6 100644 --- a/extensions/feishu/src/monitor.webhook-e2e.test.ts +++ b/extensions/feishu/src/monitor.webhook-e2e.test.ts @@ -114,6 +114,34 @@ describe("Feishu webhook signed-request e2e", () => { ); }); + it("rejects malformed short signatures with 401", async () => { + probeFeishuMock.mockResolvedValue({ ok: true, botOpenId: "bot_open_id" }); + + await withRunningWebhookMonitor( + { + accountId: "short-signature", + path: "/hook-e2e-short-signature", + verificationToken: "verify_token", + encryptKey: "encrypt_key", + }, + monitorFeishuProvider, + async (url) => { + const payload = { type: "url_verification", challenge: "challenge-token" }; + const headers = signFeishuPayload({ encryptKey: "encrypt_key", payload }); + headers["x-lark-signature"] = headers["x-lark-signature"].slice(0, 12); + + const response = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify(payload), + }); + + expect(response.status).toBe(401); + expect(await response.text()).toBe("Invalid signature"); + }, + ); + }); + it("returns 400 for invalid json before invoking the sdk", async () => { probeFeishuMock.mockResolvedValue({ ok: true, botOpenId: "bot_open_id" });