mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
Merge branch 'main' into fix/dashboard-tokenized-url-fallback-72081
This commit is contained in:
@@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Gate zalouser startup name matching [AI]. (#77411) Thanks @pgondhi987.
|
||||
- fix(device-pair): require pairing scope for pair command [AI]. (#76377) Thanks @pgondhi987.
|
||||
- fix(qqbot): keep private commands off framework surface [AI]. (#77212) Thanks @pgondhi987.
|
||||
- Memory/wiki: preserve representation from both corpora in `corpus=all` searches while backfilling unused result capacity, so memory hits are not starved by numerically higher wiki integer scores. Fixes #77337. Thanks @hclsys.
|
||||
|
||||
@@ -81,7 +81,9 @@ openclaw directory groups list --channel zalouser --query "work"
|
||||
|
||||
`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
|
||||
`channels.zalouser.allowFrom` accepts user IDs or names. During setup, names are resolved to IDs using the plugin's in-process contact lookup.
|
||||
`channels.zalouser.allowFrom` should use stable Zalo user IDs. During interactive setup, entered names can be resolved to IDs using the plugin's in-process contact lookup.
|
||||
|
||||
If a raw name remains in config, startup resolves it only when `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled. Without that opt-in, runtime sender checks are ID-only and raw names are ignored for authorization.
|
||||
|
||||
Approve via:
|
||||
|
||||
@@ -93,13 +95,13 @@ Approve via:
|
||||
- Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- Restrict to an allowlist with:
|
||||
- `channels.zalouser.groupPolicy = "allowlist"`
|
||||
- `channels.zalouser.groups` (keys should be stable group IDs; names are resolved to IDs on startup when possible)
|
||||
- `channels.zalouser.groups` (keys should be stable group IDs; names are resolved to IDs on startup only when `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled)
|
||||
- `channels.zalouser.groupAllowFrom` (controls which senders in allowed groups can trigger the bot)
|
||||
- Block all groups: `channels.zalouser.groupPolicy = "disabled"`.
|
||||
- The configure wizard can prompt for group allowlists.
|
||||
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping.
|
||||
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping only when `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled.
|
||||
- Group allowlist matching is ID-only by default. Unresolved names are ignored for auth unless `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled.
|
||||
- `channels.zalouser.dangerouslyAllowNameMatching: true` is a break-glass compatibility mode that re-enables mutable group-name matching.
|
||||
- `channels.zalouser.dangerouslyAllowNameMatching: true` is a break-glass compatibility mode that re-enables mutable startup name resolution and runtime group-name matching.
|
||||
- If `groupAllowFrom` is unset, runtime falls back to `allowFrom` for group sender checks.
|
||||
- Sender checks apply to both normal group messages and control commands (for example `/new`, `/reset`).
|
||||
|
||||
@@ -181,7 +183,7 @@ Accounts map to `zalouser` profiles in OpenClaw state. Example:
|
||||
|
||||
**Allowlist/group name didn't resolve:**
|
||||
|
||||
- Use numeric IDs in `allowFrom`/`groupAllowFrom`/`groups`, or exact friend/group names.
|
||||
- Use numeric IDs in `allowFrom`/`groupAllowFrom` and stable group IDs in `groups`. If you intentionally need exact friend/group names, enable `channels.zalouser.dangerouslyAllowNameMatching: true`.
|
||||
|
||||
**Upgraded from old CLI-based setup:**
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
Reference in New Issue
Block a user