fix(discord): gate user allowlist name resolution [AI] (#79002)

* fix: gate discord user allowlist name resolution

* docs: add changelog entry for PR merge
This commit is contained in:
Pavan Kumar Gondhi
2026-05-08 10:38:39 +05:30
committed by GitHub
parent c1edfafa3e
commit ff80167e5a
4 changed files with 85 additions and 11 deletions
+1
View File
@@ -172,6 +172,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- fix(discord): gate user allowlist name resolution [AI]. (#79002) Thanks @pgondhi987.
- fix(msteams): gate startup user allowlist resolution [AI]. (#79003) Thanks @pgondhi987.
- Harden macOS shell wrapper allowlist parsing [AI]. (#78518) Thanks @pgondhi987.
- Gateway/macOS: `openclaw gateway stop` now uses `launchctl bootout` by default instead of unconditionally calling `launchctl disable`, so KeepAlive auto-recovery still works after unexpected crashes; use the new `--disable` flag to opt into the persistent-disable behavior when a manual stop should survive reboots. Fixes #77934. Thanks @bmoran1022.
@@ -1,3 +1,4 @@
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-types";
import { createNonExitingRuntimeEnv } from "openclaw/plugin-sdk/plugin-test-runtime";
import { beforeEach, describe, expect, it, vi } from "vitest";
import * as resolveChannelsModule from "../resolve-channels.js";
@@ -44,6 +45,7 @@ describe("resolveDiscordAllowlistConfig", () => {
},
fetcher: vi.fn() as unknown as typeof fetch,
runtime,
discordConfig: { dangerouslyAllowNameMatching: true } as DiscordAccountConfig,
});
expect(result.allowFrom).toEqual(["111", "*"]);
@@ -76,6 +78,7 @@ describe("resolveDiscordAllowlistConfig", () => {
},
fetcher: vi.fn() as unknown as typeof fetch,
runtime,
discordConfig: { dangerouslyAllowNameMatching: true } as DiscordAccountConfig,
});
const logs = (runtime.log as ReturnType<typeof vi.fn>).mock.calls
@@ -135,6 +138,7 @@ describe("resolveDiscordAllowlistConfig", () => {
},
fetcher: vi.fn() as unknown as typeof fetch,
runtime,
discordConfig: {} as DiscordAccountConfig,
});
const logs = (runtime.log as ReturnType<typeof vi.fn>).mock.calls
@@ -146,4 +150,68 @@ describe("resolveDiscordAllowlistConfig", () => {
"1456350064065904867/1456744319972282449 (guild:Friends of the Crustacean 🦞🤝; channel:maintainers)",
);
});
it("keeps user allowlist names unresolved unless name matching is enabled", async () => {
const runtime = createNonExitingRuntimeEnv();
const result = await resolveDiscordAllowlistConfig({
token: "token",
allowFrom: ["Alice", "111", "*"],
guildEntries: {
"*": {
users: ["Bob", "999"],
channels: {
"*": {
users: ["Carol", "888"],
},
},
},
},
fetcher: vi.fn() as unknown as typeof fetch,
runtime,
discordConfig: {} as DiscordAccountConfig,
});
expect(result.allowFrom).toEqual(["Alice", "111", "*"]);
expect(result.guildEntries?.["*"]?.users).toEqual(["Bob", "999"]);
expect(result.guildEntries?.["*"]?.channels?.["*"]?.users).toEqual(["Carol", "888"]);
expect(resolveUsersModule.resolveDiscordUserAllowlist).not.toHaveBeenCalled();
});
it("still resolves guild and channel ids when name matching is disabled", async () => {
vi.spyOn(resolveChannelsModule, "resolveDiscordChannelAllowlist").mockResolvedValueOnce([
{
input: "ops/general",
resolved: true,
guildId: "145",
guildName: "Ops",
channelId: "246",
channelName: "general",
},
]);
const runtime = createNonExitingRuntimeEnv();
const result = await resolveDiscordAllowlistConfig({
token: "token",
allowFrom: ["Alice"],
guildEntries: {
ops: {
users: ["Bob"],
channels: {
general: {
users: ["Carol"],
},
},
},
},
fetcher: vi.fn() as unknown as typeof fetch,
runtime,
discordConfig: {} as DiscordAccountConfig,
});
expect(result.allowFrom).toEqual(["Alice"]);
expect(result.guildEntries?.["145"]?.channels?.["246"]?.users).toEqual(["Carol"]);
expect(result.guildEntries?.ops?.users).toEqual(["Bob"]);
expect(resolveChannelsModule.resolveDiscordChannelAllowlist).toHaveBeenCalledTimes(1);
expect(resolveUsersModule.resolveDiscordUserAllowlist).not.toHaveBeenCalled();
});
});
@@ -5,7 +5,8 @@ import {
patchAllowlistUsersInConfigEntries,
summarizeMapping,
} from "openclaw/plugin-sdk/allow-from";
import type { DiscordGuildEntry } from "openclaw/plugin-sdk/config-types";
import type { DiscordAccountConfig, DiscordGuildEntry } from "openclaw/plugin-sdk/config-types";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeStringEntries } from "openclaw/plugin-sdk/text-runtime";
@@ -356,6 +357,7 @@ export async function resolveDiscordAllowlistConfig(params: {
token: string;
guildEntries: unknown;
allowFrom: unknown;
discordConfig: DiscordAccountConfig;
fetcher: typeof fetch;
runtime: RuntimeEnv;
}): Promise<{ guildEntries: GuildEntries | undefined; allowFrom: string[] | undefined }> {
@@ -371,20 +373,22 @@ export async function resolveDiscordAllowlistConfig(params: {
});
}
allowFrom = await resolveAllowFromByUserAllowlist({
token: params.token,
allowFrom,
fetcher: params.fetcher,
runtime: params.runtime,
});
if (hasGuildEntries(guildEntries)) {
guildEntries = await resolveGuildEntriesByUserAllowlist({
if (isDangerousNameMatchingEnabled(params.discordConfig)) {
allowFrom = await resolveAllowFromByUserAllowlist({
token: params.token,
guildEntries,
allowFrom,
fetcher: params.fetcher,
runtime: params.runtime,
});
if (hasGuildEntries(guildEntries)) {
guildEntries = await resolveGuildEntriesByUserAllowlist({
token: params.token,
guildEntries,
fetcher: params.fetcher,
runtime: params.runtime,
});
}
}
return {
@@ -279,6 +279,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
token,
guildEntries,
allowFrom,
discordConfig: discordCfg,
fetcher: discordRestFetch,
runtime,
});