From d33c2eefce13ab3b69b7defa1cb0a73bd6c1b04d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 28 May 2026 21:45:20 -0400 Subject: [PATCH] fix: validate feishu chat page size --- extensions/feishu/src/chat-schema.ts | 6 +++- extensions/feishu/src/chat.test.ts | 54 ++++++++++++++++++++++++++++ extensions/feishu/src/chat.ts | 11 +++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/extensions/feishu/src/chat-schema.ts b/extensions/feishu/src/chat-schema.ts index e684a529e0b..5ec3399244f 100644 --- a/extensions/feishu/src/chat-schema.ts +++ b/extensions/feishu/src/chat-schema.ts @@ -1,3 +1,4 @@ +import { optionalPositiveIntegerSchema } from "openclaw/plugin-sdk/channel-actions"; import { Type, type Static } from "typebox"; const CHAT_ACTION_VALUES = ["members", "info", "member_info"] as const; @@ -11,7 +12,10 @@ export const FeishuChatSchema = Type.Object({ }), chat_id: Type.Optional(Type.String({ description: "Chat ID (from URL or event payload)" })), member_id: Type.Optional(Type.String({ description: "Member ID for member_info lookups" })), - page_size: Type.Optional(Type.Number({ description: "Page size (1-100, default 50)" })), + page_size: optionalPositiveIntegerSchema({ + maximum: 100, + description: "Page size (1-100, default 50)", + }), page_token: Type.Optional(Type.String({ description: "Pagination token" })), member_id_type: Type.Optional( Type.Unsafe<(typeof MEMBER_ID_TYPE_VALUES)[number]>({ diff --git a/extensions/feishu/src/chat.test.ts b/extensions/feishu/src/chat.test.ts index d77ec374896..174dcf60c88 100644 --- a/extensions/feishu/src/chat.test.ts +++ b/extensions/feishu/src/chat.test.ts @@ -167,6 +167,60 @@ describe("registerFeishuChatTools", () => { }); }); + it("advertises and validates member page_size as a positive integer", async () => { + const registerTool = vi.fn(); + registerFeishuChatTools( + createChatToolApi({ + config: { + channels: { + feishu: { + enabled: true, + appId: "app_id", + appSecret: "app_secret", // pragma: allowlist secret + tools: { chat: true }, + }, + }, + }, + registerTool, + }), + ); + + const tool = registerTool.mock.calls[0]?.[0]; + expect(tool?.parameters.properties.page_size).toMatchObject({ + type: "integer", + minimum: 1, + maximum: 100, + }); + + chatMembersGetMock.mockResolvedValueOnce({ + code: 0, + data: { has_more: false, items: [] }, + }); + await tool.execute("tc_page_size_string", { + action: "members", + chat_id: "oc_1", + page_size: "25", + }); + expect(chatMembersGetMock).toHaveBeenLastCalledWith({ + path: { chat_id: "oc_1" }, + params: { + page_size: 25, + page_token: undefined, + member_id_type: "open_id", + }, + }); + + const invalidResult = await tool.execute("tc_page_size_invalid", { + action: "members", + chat_id: "oc_1", + page_size: 0, + }); + expect(invalidResult.details.error).toContain( + "page_size must be a positive integer between 1 and 100", + ); + expect(chatMembersGetMock).toHaveBeenCalledTimes(1); + }); + it("skips registration when chat tool is disabled", () => { const registerTool = vi.fn(); registerFeishuChatTools( diff --git a/extensions/feishu/src/chat.ts b/extensions/feishu/src/chat.ts index 85572b9b0a5..1ef85d2a3b3 100644 --- a/extensions/feishu/src/chat.ts +++ b/extensions/feishu/src/chat.ts @@ -1,4 +1,5 @@ import type * as Lark from "@larksuiteoapi/node-sdk"; +import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { listEnabledFeishuAccounts } from "./accounts.js"; import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js"; @@ -13,6 +14,13 @@ function json(data: unknown) { }; } +function readChatPageSize(params: Record): number | undefined { + return readPositiveIntegerParam(params, "page_size", { + max: 100, + message: "page_size must be a positive integer between 1 and 100", + }); +} + export async function getChatInfo(client: Lark.Client, chatId: string) { const res = await client.im.chat.get({ path: { chat_id: chatId } }); if (res.code !== 0) { @@ -146,6 +154,7 @@ export function registerFeishuChatTools(api: OpenClawPluginApi) { description: "Feishu chat operations. Actions: members, info, member_info", parameters: FeishuChatSchema, async execute(_toolCallId, params) { + const rawParams = params as Record; const p = params as FeishuChatParams; try { const client = getClient(); @@ -158,7 +167,7 @@ export function registerFeishuChatTools(api: OpenClawPluginApi) { await getChatMembers( client, p.chat_id, - p.page_size, + readChatPageSize(rawParams), p.page_token, p.member_id_type, ),