Gate zalouser startup name matching [AI] (#77411)

* fix: gate zalouser startup name matching

* addressing codex review

* docs: add changelog entry for PR merge
This commit is contained in:
Pavan Kumar Gondhi
2026-05-04 22:47:19 +05:30
committed by GitHub
parent 37c0520a0b
commit ea75cd8971
4 changed files with 79 additions and 8 deletions

View File

@@ -3,7 +3,7 @@ import type { OpenClawConfig, PluginRuntime } from "../runtime-api.js";
import "./monitor.send-mocks.js";
import "./zalo-js.test-mocks.js";
import { resolveZalouserAccountSync } from "./accounts.js";
import { __testing } from "./monitor.js";
import { __testing, monitorZalouserProvider } from "./monitor.js";
import {
sendDeliveredZalouserMock,
sendMessageZalouserMock,
@@ -13,6 +13,11 @@ import {
import { setZalouserRuntime } from "./runtime.js";
import { createZalouserRuntimeEnv } from "./test-helpers.js";
import type { ResolvedZalouserAccount, ZaloInboundMessage } from "./types.js";
import {
listZaloFriendsMock,
listZaloGroupsMock,
startZaloListenerMock,
} from "./zalo-js.test-mocks.js";
function createAccount(): ResolvedZalouserAccount {
return {
@@ -341,6 +346,12 @@ describe("zalouser monitor group mention gating", () => {
sendTypingZalouserMock.mockClear();
sendDeliveredZalouserMock.mockClear();
sendSeenZalouserMock.mockClear();
listZaloFriendsMock.mockReset();
listZaloFriendsMock.mockResolvedValue([]);
listZaloGroupsMock.mockReset();
listZaloGroupsMock.mockResolvedValue([]);
startZaloListenerMock.mockReset();
startZaloListenerMock.mockResolvedValue({ stop: vi.fn() });
});
async function processMessageWithDefaults(params: {
@@ -374,6 +385,23 @@ describe("zalouser monitor group mention gating", () => {
expect(sendTypingZalouserMock).not.toHaveBeenCalled();
}
async function startMonitorForStartupResolution(
accountConfig: ResolvedZalouserAccount["config"],
) {
installRuntime({ commandAuthorized: false });
const abortController = new AbortController();
abortController.abort();
await monitorZalouserProvider({
account: {
...createAccount(),
config: accountConfig,
},
config: createConfig(),
runtime: createRuntimeEnv(),
abortSignal: abortController.signal,
});
}
async function expectGroupCommandAuthorizers(params: {
accountConfig: ResolvedZalouserAccount["config"];
expectedAuthorizers: Array<{ configured: boolean; allowed: boolean }>;
@@ -669,6 +697,45 @@ describe("zalouser monitor group mention gating", () => {
expect(callArg?.ctx?.To).toBe("zalouser:group:g-attacker-001");
});
it("does not resolve mutable allowlist or group names at startup by default", async () => {
listZaloFriendsMock.mockResolvedValue([{ userId: "999", displayName: "Alice" }]);
listZaloGroupsMock.mockResolvedValue([{ groupId: "g-other", name: "Trusted Team" }]);
await startMonitorForStartupResolution({
...createAccount().config,
dmPolicy: "allowlist",
allowFrom: ["Alice"],
groupPolicy: "allowlist",
groupAllowFrom: ["Alice"],
groups: {
"Trusted Team": { enabled: true },
},
});
expect(listZaloFriendsMock).not.toHaveBeenCalled();
expect(listZaloGroupsMock).not.toHaveBeenCalled();
});
it("resolves mutable allowlist and group names at startup when enabled", async () => {
listZaloFriendsMock.mockResolvedValue([{ userId: "123", displayName: "Alice" }]);
listZaloGroupsMock.mockResolvedValue([{ groupId: "g-trusted", name: "Trusted Team" }]);
await startMonitorForStartupResolution({
...createAccount().config,
dangerouslyAllowNameMatching: true,
dmPolicy: "allowlist",
allowFrom: ["Alice"],
groupPolicy: "allowlist",
groupAllowFrom: ["Alice"],
groups: {
"Trusted Team": { enabled: true },
},
});
expect(listZaloFriendsMock).toHaveBeenCalledWith("default");
expect(listZaloGroupsMock).toHaveBeenCalledWith("default");
});
it("allows group control commands when sender is in groupAllowFrom", async () => {
await expectGroupCommandAuthorizers({
accountConfig: {

View File

@@ -828,8 +828,9 @@ export async function monitorZalouserProvider(
const groupAllowFromEntries = (account.config.groupAllowFrom ?? [])
.map((entry) => normalizeZalouserEntry(String(entry)))
.filter((entry) => entry && entry !== "*");
const allowNameMatching = isDangerousNameMatchingEnabled(account.config);
if (allowFromEntries.length > 0 || groupAllowFromEntries.length > 0) {
if (allowNameMatching && (allowFromEntries.length > 0 || groupAllowFromEntries.length > 0)) {
const friends = await listZaloFriends(profile);
const byName = buildNameIndex(friends, (friend) => friend.displayName);
if (allowFromEntries.length > 0) {
@@ -869,7 +870,7 @@ export async function monitorZalouserProvider(
const groupsConfig = account.config.groups ?? {};
const groupKeys = Object.keys(groupsConfig).filter((key) => key !== "*");
if (groupKeys.length > 0) {
if (allowNameMatching && groupKeys.length > 0) {
const groups = await listZaloGroups(profile);
const byName = buildNameIndex(groups, (group) => group.name);
const mapping: string[] = [];