VYPR
High severityNVD Advisory· Published Mar 5, 2026· Updated Mar 10, 2026

OpenClaw < 2026.2.14 - Privilege Escalation in Slack Slash Command Handler via Direct Messages

CVE-2026-28392

Description

OpenClaw versions prior to 2026.2.14 contain a privilege escalation vulnerability in the Slack slash-command handler that incorrectly authorizes any direct message sender when dmPolicy is set to open (must be configured). Attackers can execute privileged slash commands via direct message to bypass allowlist and access-group restrictions.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.2.142026.2.14

Affected products

1

Patches

1
f19eabee54c4

fix(slack): gate DM slash command authorization

https://github.com/openclaw/openclawPeter SteinbergerFeb 14, 2026via ghsa
3 files changed · +51 2
  • CHANGELOG.md+1 0 modified
    @@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai
     - Security/Google Chat: deprecate `users/<email>` allowlists (treat `users/...` as immutable user id only); keep raw email allowlists for usability. Thanks @vincentkoc.
     - Security/Google Chat: reject ambiguous shared-path webhook routing when multiple webhook targets verify successfully (prevents cross-account policy-context misrouting). Thanks @vincentkoc.
     - Security/Browser: block cross-origin mutating requests to loopback browser control routes (CSRF hardening). Thanks @vincentkoc.
    +- Security/Slack: compute command authorization for DM slash commands even when `dmPolicy=open`, preventing unauthorized users from running privileged commands via DM. Thanks @christos-eth.
     - Security/Nostr: require loopback source and block cross-origin profile mutation/import attempts. Thanks @vincentkoc.
     - Security/Archive: enforce archive extraction entry/size limits to prevent resource exhaustion from high-expansion ZIP/TAR archives. Thanks @vincentkoc.
     - Security/Media: reject oversized base64-backed input media before decoding to avoid large allocations. Thanks @vincentkoc.
    
  • src/slack/monitor/slash.policy.test.ts+39 0 modified
    @@ -267,6 +267,45 @@ describe("slack slash commands access groups", () => {
         expect(respond).not.toHaveBeenCalledWith(
           expect.objectContaining({ text: "You are not authorized to use this command." }),
         );
    +    const dispatchArg = dispatchMock.mock.calls[0]?.[0] as {
    +      ctx?: { CommandAuthorized?: boolean };
    +    };
    +    expect(dispatchArg?.ctx?.CommandAuthorized).toBe(false);
    +  });
    +
    +  it("computes CommandAuthorized for DM slash commands when dmPolicy is open", async () => {
    +    const { commands, ctx, account } = createHarness({
    +      allowFrom: ["U_OWNER"],
    +      channelId: "D999",
    +      channelName: "directmessage",
    +      resolveChannelName: async () => ({ name: "directmessage", type: "im" }),
    +    });
    +    registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never });
    +
    +    const handler = [...commands.values()][0];
    +    if (!handler) {
    +      throw new Error("Missing slash handler");
    +    }
    +
    +    const respond = vi.fn().mockResolvedValue(undefined);
    +    await handler({
    +      command: {
    +        user_id: "U_ATTACKER",
    +        user_name: "Mallory",
    +        channel_id: "D999",
    +        channel_name: "directmessage",
    +        text: "hello",
    +        trigger_id: "t1",
    +      },
    +      ack: vi.fn().mockResolvedValue(undefined),
    +      respond,
    +    });
    +
    +    expect(dispatchMock).toHaveBeenCalledTimes(1);
    +    const dispatchArg = dispatchMock.mock.calls[0]?.[0] as {
    +      ctx?: { CommandAuthorized?: boolean };
    +    };
    +    expect(dispatchArg?.ctx?.CommandAuthorized).toBe(false);
       });
     
       it("enforces access-group gating when lookup fails for private channels", async () => {
    
  • src/slack/monitor/slash.ts+11 2 modified
    @@ -204,7 +204,9 @@ export function registerSlackMonitorSlashCommands(params: {
           const effectiveAllowFrom = normalizeAllowList([...ctx.allowFrom, ...storeAllowFrom]);
           const effectiveAllowFromLower = normalizeAllowListLower(effectiveAllowFrom);
     
    -      let commandAuthorized = true;
    +      // Privileged command surface: compute CommandAuthorized, don't assume true.
    +      // Keep this aligned with the Slack message path (message-handler/prepare.ts).
    +      let commandAuthorized = false;
           let channelConfig: SlackChannelConfigResolved | null = null;
           if (isDirectMessage) {
             if (!ctx.dmEnabled || ctx.dmPolicy === "disabled") {
    @@ -256,7 +258,6 @@ export function registerSlackMonitorSlashCommands(params: {
                 }
                 return;
               }
    -          commandAuthorized = true;
             }
           }
     
    @@ -322,13 +323,21 @@ export function registerSlackMonitorSlashCommands(params: {
             id: command.user_id,
             name: senderName,
           }).allowed;
    +      // DMs: allow chatting in dmPolicy=open, but keep privileged command gating intact by setting
    +      // CommandAuthorized based on allowlists/access-groups (downstream decides which commands need it).
    +      commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
    +        useAccessGroups: ctx.useAccessGroups,
    +        authorizers: [{ configured: effectiveAllowFromLower.length > 0, allowed: ownerAllowed }],
    +        modeWhenAccessGroupsOff: "configured",
    +      });
           if (isRoomish) {
             commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
               useAccessGroups: ctx.useAccessGroups,
               authorizers: [
                 { configured: effectiveAllowFromLower.length > 0, allowed: ownerAllowed },
                 { configured: channelUsersAllowlistConfigured, allowed: channelUserAllowed },
               ],
    +          modeWhenAccessGroupsOff: "configured",
             });
             if (ctx.useAccessGroups && !commandAuthorized) {
               await respond({
    

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.