Fix Discord native commands bypassing group DM channel allowlist (#57735)

* Fix Discord native commands bypassing group DM channel allowlist

* Fix linting

* Update tests
This commit is contained in:
Devin Robison
2026-03-30 11:17:36 -06:00
committed by GitHub
parent dd17dae3e5
commit 8fdb19676a
3 changed files with 151 additions and 7 deletions

View File

@@ -43,10 +43,20 @@ function createNativeCommand(
if (!command) {
throw new Error(`missing native command: ${name}`);
}
const cfg = (opts?.cfg ?? {}) as ReturnType<typeof loadConfig>;
const discordConfig = (opts?.discordConfig ?? {}) as NonNullable<
const baseCfg = (opts?.cfg ?? {}) as ReturnType<typeof loadConfig>;
const discordConfig = (opts?.discordConfig ?? baseCfg.channels?.discord ?? {}) as NonNullable<
OpenClawConfig["channels"]
>["discord"];
const cfg =
opts?.discordConfig === undefined
? baseCfg
: ({
...baseCfg,
channels: {
...baseCfg.channels,
discord: discordConfig,
},
} as ReturnType<typeof loadConfig>);
return createDiscordNativeCommand({
command,
cfg,
@@ -199,6 +209,57 @@ describe("createDiscordNativeCommand option wiring", () => {
expect(respond).toHaveBeenCalledWith([]);
});
it("returns no autocomplete choices for group DMs outside dm.groupChannels", async () => {
const discordConfig = {
dm: {
enabled: true,
policy: "open",
groupEnabled: true,
groupChannels: ["allowed-group"],
},
} satisfies NonNullable<OpenClawConfig["channels"]>["discord"];
const command = createNativeCommand("think", {
cfg: {
commands: {
allowFrom: {
discord: ["user:allowed-user"],
},
},
} as ReturnType<typeof loadConfig>,
discordConfig,
});
const level = requireOption(command, "level");
const autocomplete = readAutocomplete(level);
if (typeof autocomplete !== "function") {
throw new Error("think level option did not wire autocomplete");
}
const respond = vi.fn(async (_choices: unknown[]) => undefined);
await autocomplete({
user: {
id: "allowed-user",
username: "allowed",
globalName: "Allowed",
},
channel: {
type: ChannelType.GroupDM,
id: "blocked-group",
name: "Blocked Group",
},
guild: undefined,
rawData: {
member: { roles: [] },
},
options: {
getFocused: () => ({ value: "xh" }),
},
respond,
client: {},
} as never);
expect(respond).toHaveBeenCalledWith([]);
});
it("truncates Discord command and option descriptions to Discord's limit", () => {
const longDescription = "x".repeat(140);
const cfg = {} as ReturnType<typeof loadConfig>;