diff --git a/extensions/slack/src/monitor/auth.test.ts b/extensions/slack/src/monitor/auth.test.ts index 2e0de1e4329..51cab978e1c 100644 --- a/extensions/slack/src/monitor/auth.test.ts +++ b/extensions/slack/src/monitor/auth.test.ts @@ -1,7 +1,8 @@ -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { SlackMonitorContext } from "./context.js"; const readChannelIngressStoreAllowFromForDmPolicyMock = vi.hoisted(() => vi.fn()); +let authorizeSlackBotRoomMessage: typeof import("./auth.js").authorizeSlackBotRoomMessage; let authorizeSlackSystemEventSender: typeof import("./auth.js").authorizeSlackSystemEventSender; let clearSlackAllowFromCacheForTest: typeof import("./auth.js").clearSlackAllowFromCacheForTest; let resolveSlackEffectiveAllowFrom: typeof import("./auth.js").resolveSlackEffectiveAllowFrom; @@ -109,12 +110,54 @@ describe("resolveSlackEffectiveAllowFrom", () => { describe("authorizeSlackSystemEventSender", () => { beforeAll(async () => { - ({ authorizeSlackSystemEventSender, clearSlackAllowFromCacheForTest } = - await import("./auth.js")); + ({ + authorizeSlackBotRoomMessage, + authorizeSlackSystemEventSender, + clearSlackAllowFromCacheForTest, + } = await import("./auth.js")); }); beforeEach(() => { clearSlackAllowFromCacheForTest(); + delete process.env.OPENCLAW_SLACK_CHANNEL_MEMBERS_CACHE_TTL_MS; + }); + + afterEach(() => { + delete process.env.OPENCLAW_SLACK_CHANNEL_MEMBERS_CACHE_TTL_MS; + }); + + it("ignores non-decimal channel member cache ttl env values", async () => { + process.env.OPENCLAW_SLACK_CHANNEL_MEMBERS_CACHE_TTL_MS = "0x0"; + const conversationsMembers = vi.fn(async () => ({ + members: ["UOWNER"], + response_metadata: {}, + })); + const ctx = { + allowFrom: [], + accountId: "main", + allowNameMatching: false, + app: { client: { conversations: { members: conversationsMembers } } }, + botToken: "xoxb-test", + } as unknown as SlackMonitorContext; + + await expect( + authorizeSlackBotRoomMessage({ + ctx, + channelId: "C1", + senderId: "U_BOT", + allowFromLower: ["uowner"], + }), + ).resolves.toBe(true); + await expect( + authorizeSlackBotRoomMessage({ + ctx, + channelId: "C1", + senderId: "U_BOT", + allowFromLower: ["uowner"], + }), + ).resolves.toBe(true); + + expect(conversationsMembers).toHaveBeenCalledTimes(1); }); it("keeps non-interactive channel senders open when only global allowFrom is configured", async () => { diff --git a/extensions/slack/src/monitor/auth.ts b/extensions/slack/src/monitor/auth.ts index 91c24d0dcbb..4652afd272a 100644 --- a/extensions/slack/src/monitor/auth.ts +++ b/extensions/slack/src/monitor/auth.ts @@ -145,8 +145,8 @@ function readSlackCacheTtlMs(envName: string, fallback: number): number { if (!raw) { return fallback; } - const parsed = Number(raw); - return Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : fallback; + const parsed = /^\d+$/.test(raw) ? Number(raw) : Number.NaN; + return Number.isSafeInteger(parsed) ? parsed : fallback; } function getChannelMembersCache(