VYPR
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.

PackageAffected versionsPatched versions
openclawnpm
< 2026.3.312026.3.31

Affected products

1
  • cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*
    Range: <2026.3.31

Patches

1
8fdb19676ab4

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

https://github.com/openclaw/openclawDevin RobisonMar 30, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.