From cddab06112a0ab0910f45165f84c7ada9c7c2d32 Mon Sep 17 00:00:00 2001 From: Sarang19 Date: Fri, 30 Jan 2026 10:44:35 +0530 Subject: [PATCH] fix(whatsapp): allow media from allowlisted groups without groupAllowFrom --- .../access-control.groups-allowlist.test.ts | 209 ++++++++++++++++++ src/web/inbound/access-control.ts | 59 +++-- src/web/inbound/monitor.ts | 1 + 3 files changed, 247 insertions(+), 22 deletions(-) create mode 100644 src/web/inbound/access-control.groups-allowlist.test.ts diff --git a/src/web/inbound/access-control.groups-allowlist.test.ts b/src/web/inbound/access-control.groups-allowlist.test.ts new file mode 100644 index 000000000..4beb3b613 --- /dev/null +++ b/src/web/inbound/access-control.groups-allowlist.test.ts @@ -0,0 +1,209 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { checkInboundAccessControl } from "./access-control.js"; + +const sendMessageMock = vi.fn(); +const readAllowFromStoreMock = vi.fn(); +const upsertPairingRequestMock = vi.fn(); + +let config: Record = {}; + +vi.mock("../../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: () => config, + }; +}); + +vi.mock("../../pairing/pairing-store.js", () => ({ + readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), + upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), +})); + +beforeEach(() => { + config = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15550009999"], + groupPolicy: "allowlist", + }, + }, + }; + sendMessageMock.mockReset().mockResolvedValue(undefined); + readAllowFromStoreMock.mockReset().mockResolvedValue([]); + upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true }); +}); + +describe("checkInboundAccessControl group allowlist (#3375)", () => { + it("allows messages from any sender when group is in groups allowlist", async () => { + config = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15550009999"], + groupPolicy: "allowlist", + groups: { + "123456789@g.us": { requireMention: false }, + }, + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "default", + from: "123456789@g.us", + selfE164: "+15550009999", + senderE164: "+15550001111", // Sender NOT in allowFrom + group: true, + groupId: "123456789@g.us", + pushName: "Unknown User", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "123456789@g.us", + }); + + expect(result.allowed).toBe(true); + }); + + it("blocks messages when group is NOT in groups allowlist and sender NOT in groupAllowFrom", async () => { + config = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15550009999"], + groupPolicy: "allowlist", + groupAllowFrom: ["+15550009999"], + // No groups config - so group is not in allowlist + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "default", + from: "123456789@g.us", + selfE164: "+15550009999", + senderE164: "+15550001111", // Sender NOT in groupAllowFrom + group: true, + groupId: "123456789@g.us", + pushName: "Unknown User", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "123456789@g.us", + }); + + expect(result.allowed).toBe(false); + }); + + it("allows messages when sender is in groupAllowFrom even if group not in allowlist", async () => { + config = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15550009999"], + groupPolicy: "allowlist", + groupAllowFrom: ["+15550001111"], + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "default", + from: "123456789@g.us", + selfE164: "+15550009999", + senderE164: "+15550001111", // Sender IS in groupAllowFrom + group: true, + groupId: "123456789@g.us", + pushName: "Allowed User", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "123456789@g.us", + }); + + expect(result.allowed).toBe(true); + }); + + it("blocks all group messages when groupPolicy is disabled", async () => { + config = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15550009999"], + groupPolicy: "disabled", + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "default", + from: "123456789@g.us", + selfE164: "+15550009999", + senderE164: "+15550001111", + group: true, + groupId: "123456789@g.us", + pushName: "User", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "123456789@g.us", + }); + + expect(result.allowed).toBe(false); + }); + + it("blocks group messages when groupPolicy is allowlist but no groups configured and no groupAllowFrom", async () => { + config = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15550009999"], + groupPolicy: "allowlist", + // No groups and no groupAllowFrom + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "default", + from: "123456789@g.us", + selfE164: "+15550009999", + senderE164: "+15550001111", + group: true, + groupId: "123456789@g.us", + pushName: "User", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "123456789@g.us", + }); + + expect(result.allowed).toBe(false); + }); + + it("allows messages with groupAllowFrom wildcard", async () => { + config = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15550009999"], + groupPolicy: "allowlist", + groupAllowFrom: ["*"], + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "default", + from: "123456789@g.us", + selfE164: "+15550009999", + senderE164: "+15550001111", + group: true, + groupId: "123456789@g.us", + pushName: "Any User", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "123456789@g.us", + }); + + expect(result.allowed).toBe(true); + }); +}); diff --git a/src/web/inbound/access-control.ts b/src/web/inbound/access-control.ts index 891712015..2084882fe 100644 --- a/src/web/inbound/access-control.ts +++ b/src/web/inbound/access-control.ts @@ -23,6 +23,8 @@ export async function checkInboundAccessControl(params: { selfE164: string | null; senderE164: string | null; group: boolean; + /** Group JID for checking groups allowlist (only applicable when group=true) */ + groupId?: string; pushName?: string; isFromMe: boolean; messageTimestampMs?: number; @@ -90,28 +92,41 @@ export async function checkInboundAccessControl(params: { }; } if (params.group && groupPolicy === "allowlist") { - if (!groupAllowFrom || groupAllowFrom.length === 0) { - logVerbose("Blocked group message (groupPolicy: allowlist, no groupAllowFrom)"); - return { - allowed: false, - shouldMarkRead: false, - isSelfChat, - resolvedAccountId: account.accountId, - }; - } - const senderAllowed = - groupHasWildcard || - (params.senderE164 != null && normalizedGroupAllowFrom.includes(params.senderE164)); - if (!senderAllowed) { - logVerbose( - `Blocked group message from ${params.senderE164 ?? "unknown sender"} (groupPolicy: allowlist)`, - ); - return { - allowed: false, - shouldMarkRead: false, - isSelfChat, - resolvedAccountId: account.accountId, - }; + // Check if group is explicitly in the groups allowlist. + // If so, allow messages from all participants in this group (#3375). + const groups = account.groups; + const groupInAllowlist = params.groupId && groups && params.groupId in groups; + + if (groupInAllowlist) { + logVerbose(`Allowing message from allowlisted group ${params.groupId}`); + // Continue to allow; don't return early so DM checks are skipped but message proceeds. + } else { + // Group not in allowlist - fall back to sender-based filtering via groupAllowFrom + if (!groupAllowFrom || groupAllowFrom.length === 0) { + logVerbose( + `Blocked group message from ${params.groupId ?? "unknown group"} (groupPolicy: allowlist, group not in allowlist, no groupAllowFrom)`, + ); + return { + allowed: false, + shouldMarkRead: false, + isSelfChat, + resolvedAccountId: account.accountId, + }; + } + const senderAllowed = + groupHasWildcard || + (params.senderE164 != null && normalizedGroupAllowFrom.includes(params.senderE164)); + if (!senderAllowed) { + logVerbose( + `Blocked group message from ${params.senderE164 ?? "unknown sender"} in ${params.groupId ?? "unknown group"} (groupPolicy: allowlist, sender not in groupAllowFrom)`, + ); + return { + allowed: false, + shouldMarkRead: false, + isSelfChat, + resolvedAccountId: account.accountId, + }; + } } } diff --git a/src/web/inbound/monitor.ts b/src/web/inbound/monitor.ts index 3633cbce9..2e0f0a184 100644 --- a/src/web/inbound/monitor.ts +++ b/src/web/inbound/monitor.ts @@ -183,6 +183,7 @@ export async function monitorWebInbox(options: { selfE164, senderE164, group, + groupId: group ? remoteJid : undefined, pushName: msg.pushName ?? undefined, isFromMe: Boolean(msg.key?.fromMe), messageTimestampMs,