diff --git a/CHANGELOG.md b/CHANGELOG.md index 07b72149af7..d882c0c83cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,7 @@ Docs: https://docs.openclaw.ai - Security/Config: redact sensitive-looking dynamic catchall keys in `config.get` snapshots (for example `env.*` and `skills.entries.*.env.*`) and preserve round-trip restore behavior for those redacted sentinels. Thanks @merc1305. - Tests/Vitest: tier local parallel worker defaults by host memory, keep gateway serial by default on non-high-memory hosts, and document a low-profile fallback command for memory-constrained land/gate runs to prevent local OOMs. (#24719) Thanks @ngutman. +- WhatsApp/Group policy: fix `groupAllowFrom` sender filtering when `groupPolicy: "allowlist"` is set without explicit `groups` — previously all group messages were blocked even for allowlisted senders. (#24670) - Agents/Context pruning: extend `cache-ttl` eligibility to Moonshot/Kimi and ZAI/GLM providers (including OpenRouter model refs), so `contextPruning.mode: "cache-ttl"` is no longer silently skipped for those sessions. (#24497) Thanks @lailoo. - Tools/web_search: add `provider: "kimi"` (Moonshot) support with key/config schema wiring and a corrected two-step `$web_search` tool flow that echoes tool results before final synthesis, including citation extraction from search results. (#16616, #18822) Thanks @adshine. - Media understanding/Video: add a native Moonshot video provider and include Moonshot in auto video key detection, plus refactor video execution to honor `entry/config/provider` baseUrl+header precedence (matching audio behavior). (#12063) Thanks @xiaoyaner0201. diff --git a/src/config/group-policy.test.ts b/src/config/group-policy.test.ts index 8151f36363b..a3ca8ad5327 100644 --- a/src/config/group-policy.test.ts +++ b/src/config/group-policy.test.ts @@ -89,6 +89,46 @@ describe("resolveChannelGroupPolicy", () => { expect(policy.allowlistEnabled).toBe(true); expect(policy.allowed).toBe(false); }); + + it("allows groups when groupPolicy=allowlist with hasGroupAllowFrom but no groups", () => { + const cfg = { + channels: { + whatsapp: { + groupPolicy: "allowlist", + }, + }, + } as OpenClawConfig; + + const policy = resolveChannelGroupPolicy({ + cfg, + channel: "whatsapp", + groupId: "123@g.us", + hasGroupAllowFrom: true, + }); + + expect(policy.allowlistEnabled).toBe(true); + expect(policy.allowed).toBe(true); + }); + + it("still fails closed when groupPolicy=allowlist without groups or groupAllowFrom", () => { + const cfg = { + channels: { + whatsapp: { + groupPolicy: "allowlist", + }, + }, + } as OpenClawConfig; + + const policy = resolveChannelGroupPolicy({ + cfg, + channel: "whatsapp", + groupId: "123@g.us", + hasGroupAllowFrom: false, + }); + + expect(policy.allowlistEnabled).toBe(true); + expect(policy.allowed).toBe(false); + }); }); describe("resolveToolsBySender", () => { diff --git a/src/config/group-policy.ts b/src/config/group-policy.ts index fe8b1542a12..fdb028f9f7c 100644 --- a/src/config/group-policy.ts +++ b/src/config/group-policy.ts @@ -328,6 +328,8 @@ export function resolveChannelGroupPolicy(params: { groupId?: string | null; accountId?: string | null; groupIdCaseInsensitive?: boolean; + /** When true, sender-level filtering (groupAllowFrom) is configured upstream. */ + hasGroupAllowFrom?: boolean; }): ChannelGroupPolicy { const { cfg, channel } = params; const groups = resolveChannelGroups(cfg, channel, params.accountId); @@ -340,8 +342,14 @@ export function resolveChannelGroupPolicy(params: { : undefined; const defaultConfig = groups?.["*"]; const allowAll = allowlistEnabled && Boolean(groups && Object.hasOwn(groups, "*")); + // When groupPolicy is "allowlist" with groupAllowFrom but no explicit groups, + // allow the group through — sender-level filtering handles access control. + const senderFilterBypass = + groupPolicy === "allowlist" && !hasGroups && Boolean(params.hasGroupAllowFrom); const allowed = - groupPolicy === "disabled" ? false : !allowlistEnabled || allowAll || Boolean(groupConfig); + groupPolicy === "disabled" + ? false + : !allowlistEnabled || allowAll || Boolean(groupConfig) || senderFilterBypass; return { allowlistEnabled, allowed, diff --git a/src/web/auto-reply/monitor/group-activation.ts b/src/web/auto-reply/monitor/group-activation.ts index aeb16428fbe..01f96e94528 100644 --- a/src/web/auto-reply/monitor/group-activation.ts +++ b/src/web/auto-reply/monitor/group-activation.ts @@ -16,10 +16,17 @@ export function resolveGroupPolicyFor(cfg: ReturnType, conver ChatType: "group", Provider: "whatsapp", })?.id; + const whatsappCfg = cfg.channels?.whatsapp as + | { groupAllowFrom?: string[]; allowFrom?: string[] } + | undefined; + const hasGroupAllowFrom = Boolean( + whatsappCfg?.groupAllowFrom?.length || whatsappCfg?.allowFrom?.length, + ); return resolveChannelGroupPolicy({ cfg, channel: "whatsapp", groupId: groupId ?? conversationId, + hasGroupAllowFrom, }); }