From d14be8472ee5d366c0995cf8485c3f0d3e2a288e Mon Sep 17 00:00:00 2001 From: Marcus Castro Date: Sat, 14 Feb 2026 15:41:42 -0300 Subject: [PATCH] fix(whatsapp): honor account-level dmPolicy override (#10082) (thanks @mcaxtr) Fixes openclaw#10082 (issue #8736): inbound WhatsApp DM policy now respects account-level dmPolicy overrides. --- CHANGELOG.md | 1 + .../access-control.pairing-history.test.ts | 38 +++++++++++++++++++ src/web/inbound/access-control.ts | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a67f6817a..c4faaf01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai ### Fixes - BlueBubbles: include sender identity in group chat envelopes and pass clean message text to the agent prompt, aligning with iMessage/Signal formatting. (#16210) Thanks @zerone0x. +- WhatsApp: honor per-account `dmPolicy` overrides (account-level settings now take precedence over channel defaults for inbound DMs). (#10082) Thanks @mcaxtr. - Security/Node Host: enforce `system.run` rawCommand/argv consistency to prevent allowlist/approval bypass. Thanks @christos-eth. - Security/Gateway: block `system.execApprovals.*` via `node.invoke` (use `exec.approvals.node.*` instead). Thanks @christos-eth. - CLI: fix lazy core command registration so top-level maintenance commands (`doctor`, `dashboard`, `reset`, `uninstall`) resolve correctly instead of exposing a non-functional `maintenance` placeholder command. diff --git a/src/web/inbound/access-control.pairing-history.test.ts b/src/web/inbound/access-control.pairing-history.test.ts index b5d5b721c..d907637e3 100644 --- a/src/web/inbound/access-control.pairing-history.test.ts +++ b/src/web/inbound/access-control.pairing-history.test.ts @@ -83,3 +83,41 @@ describe("checkInboundAccessControl", () => { expect(sendMessageMock).toHaveBeenCalled(); }); }); + +describe("account-level dmPolicy override (#8736)", () => { + it("uses account-level dmPolicy instead of channel-level", async () => { + // Channel-level says "pairing" but the account-level says "allowlist". + // The account-level override should take precedence, so an unauthorized + // sender should be blocked silently (no pairing reply). + config = { + channels: { + whatsapp: { + dmPolicy: "pairing", + accounts: { + work: { + dmPolicy: "allowlist", + allowFrom: ["+15559999999"], + }, + }, + }, + }, + }; + + const result = await checkInboundAccessControl({ + accountId: "work", + from: "+15550001111", + selfE164: "+15550009999", + senderE164: "+15550001111", + group: false, + pushName: "Stranger", + isFromMe: false, + sock: { sendMessage: sendMessageMock }, + remoteJid: "15550001111@s.whatsapp.net", + }); + + expect(result.allowed).toBe(false); + // dmPolicy "allowlist" should silently block — no pairing request, no reply + expect(upsertPairingRequestMock).not.toHaveBeenCalled(); + expect(sendMessageMock).not.toHaveBeenCalled(); + }); +}); diff --git a/src/web/inbound/access-control.ts b/src/web/inbound/access-control.ts index 891712015..08d97acd5 100644 --- a/src/web/inbound/access-control.ts +++ b/src/web/inbound/access-control.ts @@ -38,7 +38,7 @@ export async function checkInboundAccessControl(params: { cfg, accountId: params.accountId, }); - const dmPolicy = cfg.channels?.whatsapp?.dmPolicy ?? "pairing"; + const dmPolicy = account.dmPolicy ?? "pairing"; const configuredAllowFrom = account.allowFrom; const storeAllowFrom = await readChannelAllowFromStore("whatsapp").catch(() => []); // Without user config, default to self-only DM access so the owner can talk to themselves.