diff --git a/CHANGELOG.md b/CHANGELOG.md index bc8631d80cb..2525de6b7cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai - Zalo/plugin runtime: export `resolveClientIp` from `openclaw/plugin-sdk/zalo` so installed builds no longer crash on startup when the webhook monitor loads from the packaged extension instead of the monorepo source tree. (#46549) Thanks @No898. - Control UI/chat sessions: show human-readable labels in the grouped session dropdown again, keep unique scoped fallbacks when metadata is missing, and disambiguate duplicate labels only when needed. (#45130) thanks @luzhidong. - Configure/startup: move outbound send-deps resolution into a lightweight helper so `openclaw configure` no longer stalls after the banner while eagerly loading channel plugins. (#46301) thanks @scoootscooob. +- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#40146) ### Fixes diff --git a/extensions/zalouser/src/monitor.group-gating.test.ts b/extensions/zalouser/src/monitor.group-gating.test.ts index ef68d6f2529..9ac3b29841b 100644 --- a/extensions/zalouser/src/monitor.group-gating.test.ts +++ b/extensions/zalouser/src/monitor.group-gating.test.ts @@ -477,7 +477,37 @@ describe("zalouser monitor group mention gating", () => { }); }); - it("blocks group messages when sender is not in groupAllowFrom/allowFrom", async () => { + it("allows allowlisted group replies without inheriting the DM allowlist", async () => { + const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ + commandAuthorized: false, + replyPayload: { text: "ok" }, + }); + await __testing.processMessage({ + message: createGroupMessage({ + content: "ping @bot", + hasAnyMention: true, + wasExplicitlyMentioned: true, + senderId: "456", + }), + account: { + ...createAccount(), + config: { + ...createAccount().config, + groupPolicy: "allowlist", + allowFrom: ["123"], + groups: { + "group:g-1": { allow: true, requireMention: true }, + }, + }, + }, + config: createConfig(), + runtime: createRuntimeEnv(), + }); + + expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1); + }); + + it("blocks group messages when sender is not in groupAllowFrom", async () => { const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ commandAuthorized: false, }); @@ -493,6 +523,7 @@ describe("zalouser monitor group mention gating", () => { ...createAccount().config, groupPolicy: "allowlist", allowFrom: ["999"], + groupAllowFrom: ["999"], }, }, config: createConfig(), diff --git a/extensions/zalouser/src/monitor.ts b/extensions/zalouser/src/monitor.ts index 2bfa1be8aa4..b96ff8cdf0d 100644 --- a/extensions/zalouser/src/monitor.ts +++ b/extensions/zalouser/src/monitor.ts @@ -27,6 +27,7 @@ import { resolveOpenProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, resolveSenderCommandAuthorization, + resolveSenderScopedGroupPolicy, sendMediaWithLeadingCaption, summarizeMapping, warnMissingProviderGroupPolicyFallbackOnce, @@ -349,6 +350,10 @@ async function processMessage( const dmPolicy = account.config.dmPolicy ?? "pairing"; const configAllowFrom = (account.config.allowFrom ?? []).map((v) => String(v)); const configGroupAllowFrom = (account.config.groupAllowFrom ?? []).map((v) => String(v)); + const senderGroupPolicy = resolveSenderScopedGroupPolicy({ + groupPolicy, + groupAllowFrom: configGroupAllowFrom, + }); const shouldComputeCommandAuth = core.channel.commands.shouldComputeCommandAuthorized( commandBody, config, @@ -360,10 +365,11 @@ async function processMessage( const accessDecision = resolveDmGroupAccessWithLists({ isGroup, dmPolicy, - groupPolicy, + groupPolicy: senderGroupPolicy, allowFrom: configAllowFrom, groupAllowFrom: configGroupAllowFrom, storeAllowFrom, + groupAllowFromFallbackToAllowFrom: false, isSenderAllowed: (allowFrom) => isSenderAllowed(senderId, allowFrom), }); if (isGroup && accessDecision.decision !== "allow") { diff --git a/src/plugin-sdk/zalouser.ts b/src/plugin-sdk/zalouser.ts index 07f653223c5..4b8ef88d06d 100644 --- a/src/plugin-sdk/zalouser.ts +++ b/src/plugin-sdk/zalouser.ts @@ -61,7 +61,10 @@ export type { WizardPrompter } from "../wizard/prompts.js"; export { formatAllowFromLowercase } from "./allow-from.js"; export { resolveSenderCommandAuthorization } from "./command-auth.js"; export { resolveChannelAccountConfigBasePath } from "./config-paths.js"; -export { evaluateGroupRouteAccessForPolicy } from "./group-access.js"; +export { + evaluateGroupRouteAccessForPolicy, + resolveSenderScopedGroupPolicy, +} from "./group-access.js"; export { loadOutboundMediaFromUrl } from "./outbound-media.js"; export { createScopedPairingAccess } from "./pairing-access.js"; export { issuePairingChallenge } from "../pairing/pairing-challenge.js";