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.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.2.14 | 2026.2.14 |
Affected products
1Patches
1f19eabee54c4fix(slack): gate DM slash command authorization
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- github.com/openclaw/openclaw/commit/f19eabee54c49e9a2e264b4965edf28a2f92e657ghsapatchWEB
- github.com/advisories/GHSA-v773-r54f-q32wghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-v773-r54f-q32wghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-28392ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-privilege-escalation-in-slack-slash-command-handler-via-direct-messagesghsathird-party-advisoryWEB
- github.com/openclaw/openclaw/releases/tag/v2026.2.14ghsaWEB
News mentions
0No linked articles in our index yet.