diff --git a/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts b/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts index 14c07985fe64..db0ec8231a41 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts @@ -78,12 +78,16 @@ function shouldWarnForGroupDrop(warnKey: string): boolean { return true; } -function isOwnerSender(baseMentionConfig: MentionConfig, msg: AdmittedWebInboundMessage) { - const sender = normalizeE164(getSenderIdentity(msg).e164 ?? ""); +function isOwnerSender( + baseMentionConfig: MentionConfig, + msg: AdmittedWebInboundMessage, + authDir?: string, +) { + const sender = normalizeE164(getSenderIdentity(msg, authDir).e164 ?? ""); if (!sender) { return false; } - const owners = resolveOwnerList(baseMentionConfig, getSelfIdentity(msg).e164 ?? undefined); + const owners = resolveOwnerList(baseMentionConfig, getSelfIdentity(msg, authDir).e164 ?? undefined); return owners.includes(sender); } @@ -187,7 +191,7 @@ export async function applyGroupGating(params: ApplyGroupGatingParams) { self.e164, ); const activationCommand = parseActivationCommand(commandBody); - const owner = isOwnerSender(baseMentionConfig, params.msg); + const owner = isOwnerSender(baseMentionConfig, params.msg, params.authDir); const shouldBypassMention = owner && hasControlCommand(commandBody, params.cfg); if (activationCommand.hasCommand && !owner) { diff --git a/extensions/whatsapp/src/auto-reply/monitor/process-message.ts b/extensions/whatsapp/src/auto-reply/monitor/process-message.ts index 0105c8060d83..572f92ba6edf 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/process-message.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/process-message.ts @@ -437,6 +437,7 @@ export async function processMessage(params: { cfg: params.cfg, msg: params.msg, policy: inboundPolicy, + authDir: account.authDir, }) : undefined; const commandTurn: CommandTurnContext = isTextCommand diff --git a/extensions/whatsapp/src/inbound-policy.ts b/extensions/whatsapp/src/inbound-policy.ts index bdb5bd976989..94b865e3fd97 100644 --- a/extensions/whatsapp/src/inbound-policy.ts +++ b/extensions/whatsapp/src/inbound-policy.ts @@ -173,13 +173,14 @@ export async function resolveWhatsAppCommandAuthorized(params: { cfg: OpenClawConfig; msg: AdmittedWebInboundMessage; policy?: ResolvedWhatsAppInboundPolicy; + authDir?: string; }): Promise { const useAccessGroups = params.cfg.commands?.useAccessGroups !== false; if (!useAccessGroups) { return true; } - const self = getSelfIdentity(params.msg); + const self = getSelfIdentity(params.msg, params.authDir); const admission = requireWhatsAppInboundAdmission(params.msg); const policy = params.policy ?? @@ -189,7 +190,7 @@ export async function resolveWhatsAppCommandAuthorized(params: { selfE164: self.e164 ?? null, }); const isGroup = admission.conversation.kind === "group"; - const sender = getSenderIdentity(params.msg); + const sender = getSenderIdentity(params.msg, params.authDir); const dmSender = sender.e164 ?? admission.conversation.id; const groupSender = sender.e164 ?? ""; if (!normalizeE164(isGroup ? groupSender : dmSender)) { diff --git a/extensions/whatsapp/src/inbound/access-control.test.ts b/extensions/whatsapp/src/inbound/access-control.test.ts index 233c9d035369..4b0ac0710955 100644 --- a/extensions/whatsapp/src/inbound/access-control.test.ts +++ b/extensions/whatsapp/src/inbound/access-control.test.ts @@ -1,4 +1,7 @@ // Whatsapp tests cover access control plugin behavior. +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; import { beforeAll, describe, expect, it } from "vitest"; import type { AcceptedInboundAccessControlResult, @@ -602,6 +605,54 @@ describe("WhatsApp dmPolicy precedence", () => { expect(result.isSelfChat).toBe(false); }); + it("authorizes group commands when owner sender is a LID JID with authDir (issue #77755)", async () => { + const lidDigits = "9876543210"; + const ownerE164 = "+15550001111"; + const authDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-wa-lid-77755-")); + try { + // Write reverse LID mapping so the LID JID resolves to the owner's phone + fs.writeFileSync( + path.join(authDir, `lid-mapping-${lidDigits}_reverse.json`), + JSON.stringify(ownerE164), + ); + + const cfg = { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: [ownerE164], + }, + }, + }; + setAccessControlTestConfig(cfg); + + const result = await resolveWhatsAppCommandAuthorized({ + cfg: cfg as never, + msg: createTestWebInboundMessage({ + event: { id: "cmd-group-lid" }, + payload: { body: "/status" }, + platform: { + chatJid: "120363401234567890@g.us", + recipientJid: "+15550009999", + senderJid: `${lidDigits}@lid`, + selfE164: "+15550009999", + }, + admission: { + conversation: { + id: "120363401234567890@g.us", + kind: "group", + }, + }, + }) as never, + authDir, + }); + + expect(result).toBe(true); + } finally { + fs.rmSync(authDir, { recursive: true, force: true }); + } + }); + it("treats same-phone DMs as self-chat only when explicitly configured", async () => { const cfg = { channels: {