fix(zalouser): stop inheriting dm allowlist for groups (#46663)

This commit is contained in:
Tak Hoffman 2026-03-14 19:10:11 -05:00 committed by GitHub
parent f4aff83c51
commit 774b40467b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 3 deletions

View File

@ -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

View File

@ -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(),

View File

@ -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") {

View File

@ -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";