Medium severity5.4NVD Advisory· Published Apr 23, 2026· Updated Apr 29, 2026
CVE-2026-41348
CVE-2026-41348
Description
OpenClaw before 2026.3.31 contains an authorization bypass vulnerability in Discord slash command and autocomplete paths that fail to enforce group DM channel allowlist restrictions. Authorized Discord users can bypass channel restrictions by invoking slash commands, allowing access to restricted group DM channels.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.3.31 | 2026.3.31 |
Affected products
1Patches
18fdb19676ab4Fix Discord native commands bypassing group DM channel allowlist (#57735)
3 files changed · +151 −7
extensions/discord/src/monitor/native-command.options.test.ts+63 −2 modified@@ -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>;
extensions/discord/src/monitor/native-command.plugin-dispatch.test.ts+35 −0 modified@@ -466,6 +466,41 @@ describe("Discord native plugin command dispatch", () => { ); }); + it("rejects group DM slash commands outside dm.groupChannels before dispatch", async () => { + const cfg = { + commands: { + allowFrom: { + discord: ["user:owner"], + }, + }, + channels: { + discord: { + dm: { + enabled: true, + policy: "open", + groupEnabled: true, + groupChannels: ["allowed-group"], + }, + }, + }, + } as OpenClawConfig; + const interaction = createInteraction({ + channelType: ChannelType.GroupDM, + channelId: "blocked-group", + }); + const dispatchSpy = createDispatchSpy(); + const command = await createStatusCommand(cfg); + + await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown); + + expect(dispatchSpy).not.toHaveBeenCalled(); + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: "This group DM is not allowed.", + }), + ); + }); + it("executes matched plugin commands directly without invoking the agent dispatcher", async () => { const cfg = createConfig(); const commandSpec: NativeCommandSpec = {
extensions/discord/src/monitor/native-command.ts+53 −5 modified@@ -53,6 +53,7 @@ import { isDiscordGroupAllowedByPolicy, normalizeDiscordAllowList, normalizeDiscordSlug, + resolveGroupDmAllow, resolveDiscordChannelConfigWithFallback, resolveDiscordAllowListMatch, resolveDiscordGuildEntry, @@ -283,6 +284,33 @@ function shouldBypassConfiguredAcpEnsure(commandName: string): boolean { return normalized === "acp" || normalized === "new" || normalized === "reset"; } +function resolveDiscordNativeGroupDmAccess(params: { + isGroupDm: boolean; + groupEnabled?: boolean; + groupChannels?: string[]; + channelId: string; + channelName?: string; + channelSlug: string; +}): { allowed: true } | { allowed: false; reason: "disabled" | "not-allowlisted" } { + if (!params.isGroupDm) { + return { allowed: true }; + } + if (params.groupEnabled === false) { + return { allowed: false, reason: "disabled" }; + } + if ( + !resolveGroupDmAllow({ + channels: params.groupChannels, + channelId: params.channelId, + channelName: params.channelName, + channelSlug: params.channelSlug, + }) + ) { + return { allowed: false, reason: "not-allowlisted" }; + } + return { allowed: true }; +} + async function resolveDiscordNativeAutocompleteAuthorized(params: { interaction: AutocompleteInteraction; cfg: ReturnType<typeof loadConfig>; @@ -421,7 +449,15 @@ async function resolveDiscordNativeAutocompleteAuthorized(params: { return false; } } - if (isGroupDm && discordConfig?.dm?.groupEnabled === false) { + const groupDmAccess = resolveDiscordNativeGroupDmAccess({ + isGroupDm, + groupEnabled: discordConfig?.dm?.groupEnabled, + groupChannels: discordConfig?.dm?.groupChannels, + channelId: rawChannelId, + channelName, + channelSlug, + }); + if (!groupDmAccess.allowed) { return false; } if (!isDirectMessage) { @@ -832,6 +868,22 @@ async function dispatchDiscordCommandInteraction(params: { return; } } + const groupDmAccess = resolveDiscordNativeGroupDmAccess({ + isGroupDm, + groupEnabled: discordConfig?.dm?.groupEnabled, + groupChannels: discordConfig?.dm?.groupChannels, + channelId: rawChannelId, + channelName, + channelSlug, + }); + if (!groupDmAccess.allowed) { + await respond( + groupDmAccess.reason === "disabled" + ? "Discord group DMs are disabled." + : "This group DM is not allowed.", + ); + return; + } if (!isDirectMessage) { const { hasAccessRestrictions, memberAllowed } = resolveDiscordMemberAccessState({ channelConfig, @@ -866,10 +918,6 @@ async function dispatchDiscordCommandInteraction(params: { return; } } - if (isGroupDm && discordConfig?.dm?.groupEnabled === false) { - await respond("Discord group DMs are disabled."); - return; - } const menu = resolveCommandArgMenu({ command,
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/openclaw/openclaw/commit/8fdb19676ab44cf85d47ee13c578195f2e527591nvdPatchWEB
- github.com/advisories/GHSA-rvvf-6vh3-9j43ghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-rvvf-6vh3-9j43nvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-41348ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-group-dm-channel-allowlist-bypass-via-discord-slash-commandsnvdThird Party AdvisoryWEB
- github.com/openclaw/openclaw/releases/tag/v2026.3.31ghsaWEB
News mentions
0No linked articles in our index yet.