Medium severity5.4NVD Advisory· Published Apr 23, 2026· Updated May 1, 2026
CVE-2026-41358
CVE-2026-41358
Description
OpenClaw before 2026.4.2 fails to filter Slack thread context by sender allowlist, allowing non-allowlisted messages to enter agent context. Attackers can inject unauthorized thread messages through allowlisted user replies to bypass sender access controls and manipulate model context.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.4.2 | 2026.4.2 |
Affected products
1Patches
1ac5bc4fb37beSlack: filter thread context by allowlist (#58380)
7 files changed · +526 −8
CHANGELOG.md+1 −0 modified@@ -37,6 +37,7 @@ Docs: https://docs.openclaw.ai - Podman/launch: remove noisy container output from `scripts/run-openclaw-podman.sh` and align the Podman install guidance with the quieter startup flow. (#59368) Thanks @sallyom. - MS Teams/streaming: strip already-streamed text from fallback block delivery when replies exceed the 4000-character streaming limit so long responses stop duplicating content. (#59297) Thanks @bradgroux. - MS Teams/logging: format non-`Error` failures with the shared unknown-error helper so logs stop collapsing caught SDK or Axios objects into `[object Object]`. (#59321) Thanks @bradgroux. +- Slack/thread context: filter thread starter and history by the effective conversation allowlist without dropping valid open-room, DM, or group DM context. (#58380) ## 2026.4.1-beta.1
extensions/slack/src/monitor/media.ts+11 −1 modified@@ -337,6 +337,7 @@ export async function resolveSlackAttachmentContent(params: { export type SlackThreadStarter = { text: string; userId?: string; + botId?: string; ts?: string; files?: SlackFile[]; }; @@ -391,7 +392,15 @@ export async function resolveSlackThreadStarter(params: { ts: params.threadTs, limit: 1, inclusive: true, - })) as { messages?: Array<{ text?: string; user?: string; ts?: string; files?: SlackFile[] }> }; + })) as { + messages?: Array<{ + text?: string; + user?: string; + bot_id?: string; + ts?: string; + files?: SlackFile[]; + }>; + }; const message = response?.messages?.[0]; const text = (message?.text ?? "").trim(); if (!message || !text) { @@ -400,6 +409,7 @@ export async function resolveSlackThreadStarter(params: { const starter: SlackThreadStarter = { text, userId: message.user, + botId: message.bot_id, ts: message.ts, files: message.files, };
extensions/slack/src/monitor/message-handler/prepare.test-helpers.ts+2 −1 modified@@ -2,14 +2,15 @@ import type { App } from "@slack/bolt"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; import type { ResolvedSlackAccount } from "../../accounts.js"; +import type { SlackChannelConfigEntries } from "../channel-config.js"; import { createSlackMonitorContext } from "../context.js"; export function createInboundSlackTestContext(params: { cfg: OpenClawConfig; appClient?: App["client"]; defaultRequireMention?: boolean; replyToMode?: "off" | "all" | "first"; - channelsConfig?: Record<string, { systemPrompt: string }>; + channelsConfig?: SlackChannelConfigEntries; }) { return createSlackMonitorContext({ cfg: params.cfg,
extensions/slack/src/monitor/message-handler/prepare.thread-context-allowlist.test.ts+288 −0 added@@ -0,0 +1,288 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import type { App } from "@slack/bolt"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import type { SlackMessageEvent } from "../../types.js"; + +type PrepareSlackMessage = typeof import("./prepare.js").prepareSlackMessage; +type CreateInboundSlackTestContext = + typeof import("./prepare.test-helpers.js").createInboundSlackTestContext; +type CreateSlackTestAccount = typeof import("./prepare.test-helpers.js").createSlackTestAccount; + +let prepareSlackMessage: PrepareSlackMessage; +let createInboundSlackTestContext: CreateInboundSlackTestContext; +let createSlackTestAccount: CreateSlackTestAccount; +let fixtureRoot = ""; +let caseId = 0; + +async function loadSlackPrepareModules() { + const [{ prepareSlackMessage: loadedPrepareSlackMessage }, helpers] = await Promise.all([ + import("./prepare.js"), + import("./prepare.test-helpers.js"), + ]); + prepareSlackMessage = loadedPrepareSlackMessage; + createInboundSlackTestContext = helpers.createInboundSlackTestContext; + createSlackTestAccount = helpers.createSlackTestAccount; +} + +function makeTmpStorePath() { + if (!fixtureRoot) { + throw new Error("fixtureRoot missing"); + } + const dir = path.join(fixtureRoot, `case-${caseId++}`); + fs.mkdirSync(dir); + return path.join(dir, "sessions.json"); +} + +describe("prepareSlackMessage thread context allowlists", () => { + beforeAll(async () => { + await loadSlackPrepareModules(); + fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-slack-room-thread-context-")); + }); + + afterAll(() => { + if (fixtureRoot) { + fs.rmSync(fixtureRoot, { recursive: true, force: true }); + fixtureRoot = ""; + } + }); + + it("uses room users allowlist for thread context filtering", async () => { + const replies = vi + .fn() + .mockResolvedValueOnce({ + messages: [{ text: "starter from room user", user: "U1", ts: "100.000" }], + }) + .mockResolvedValueOnce({ + messages: [ + { text: "starter from room user", user: "U1", ts: "100.000" }, + { text: "assistant reply", bot_id: "B1", ts: "100.500" }, + { text: "allowed follow-up", user: "U1", ts: "100.800" }, + { text: "current message", user: "U1", ts: "101.000" }, + ], + response_metadata: { next_cursor: "" }, + }); + const storePath = makeTmpStorePath(); + const ctx = createInboundSlackTestContext({ + cfg: { + session: { store: storePath }, + channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } }, + } as OpenClawConfig, + appClient: { conversations: { replies } } as unknown as App["client"], + defaultRequireMention: false, + replyToMode: "all", + channelsConfig: { + C123: { + users: ["U1"], + requireMention: false, + }, + }, + }); + ctx.allowFrom = ["u-owner"]; + ctx.resolveUserName = async (id: string) => ({ + name: id === "U1" ? "Alice" : "Owner", + }); + ctx.resolveChannelName = async () => ({ name: "general", type: "channel" }); + + const prepared = await prepareSlackMessage({ + ctx, + account: createSlackTestAccount({ + replyToMode: "all", + thread: { initialHistoryLimit: 20 }, + }), + message: { + channel: "C123", + channel_type: "channel", + user: "U1", + text: "current message", + ts: "101.000", + thread_ts: "100.000", + } as SlackMessageEvent, + opts: { source: "message" }, + }); + + expect(prepared).toBeTruthy(); + expect(prepared!.ctxPayload.ThreadStarterBody).toBe("starter from room user"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("starter from room user"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("assistant reply"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("allowed follow-up"); + expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("current message"); + expect(replies).toHaveBeenCalledTimes(2); + }); + + it("does not apply the owner allowlist to open-room thread context", async () => { + const replies = vi + .fn() + .mockResolvedValueOnce({ + messages: [{ text: "starter from open room", user: "U2", ts: "200.000" }], + }) + .mockResolvedValueOnce({ + messages: [ + { text: "starter from open room", user: "U2", ts: "200.000" }, + { text: "assistant reply", bot_id: "B1", ts: "200.500" }, + { text: "open-room follow-up", user: "U2", ts: "200.800" }, + { text: "current message", user: "U2", ts: "201.000" }, + ], + response_metadata: { next_cursor: "" }, + }); + const storePath = makeTmpStorePath(); + const ctx = createInboundSlackTestContext({ + cfg: { + session: { store: storePath }, + channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } }, + } as OpenClawConfig, + appClient: { conversations: { replies } } as unknown as App["client"], + defaultRequireMention: false, + replyToMode: "all", + channelsConfig: { + C124: { + requireMention: false, + }, + }, + }); + ctx.allowFrom = ["u-owner"]; + ctx.resolveUserName = async (id: string) => ({ + name: id === "U2" ? "Bob" : "Owner", + }); + ctx.resolveChannelName = async () => ({ name: "general", type: "channel" }); + + const prepared = await prepareSlackMessage({ + ctx, + account: createSlackTestAccount({ + replyToMode: "all", + thread: { initialHistoryLimit: 20 }, + }), + message: { + channel: "C124", + channel_type: "channel", + user: "U2", + text: "current message", + ts: "201.000", + thread_ts: "200.000", + } as SlackMessageEvent, + opts: { source: "message" }, + }); + + expect(prepared).toBeTruthy(); + expect(prepared!.ctxPayload.ThreadStarterBody).toBe("starter from open room"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("starter from open room"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("assistant reply"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("open-room follow-up"); + expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("current message"); + expect(replies).toHaveBeenCalledTimes(2); + }); + + it("does not apply the owner allowlist to open DMs when dmPolicy is open", async () => { + const replies = vi + .fn() + .mockResolvedValueOnce({ + messages: [{ text: "starter from open dm", user: "U3", ts: "300.000" }], + }) + .mockResolvedValueOnce({ + messages: [ + { text: "starter from open dm", user: "U3", ts: "300.000" }, + { text: "assistant reply", bot_id: "B1", ts: "300.500" }, + { text: "dm follow-up", user: "U3", ts: "300.800" }, + { text: "current message", user: "U3", ts: "301.000" }, + ], + response_metadata: { next_cursor: "" }, + }); + const storePath = makeTmpStorePath(); + const ctx = createInboundSlackTestContext({ + cfg: { + session: { store: storePath }, + channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } }, + } as OpenClawConfig, + appClient: { conversations: { replies } } as unknown as App["client"], + defaultRequireMention: false, + replyToMode: "all", + }); + ctx.allowFrom = ["u-owner"]; + ctx.resolveUserName = async (id: string) => ({ + name: id === "U3" ? "Dana" : "Owner", + }); + + const prepared = await prepareSlackMessage({ + ctx, + account: createSlackTestAccount({ + replyToMode: "all", + thread: { initialHistoryLimit: 20 }, + }), + message: { + channel: "D300", + channel_type: "im", + user: "U3", + text: "current message", + ts: "301.000", + thread_ts: "300.000", + } as SlackMessageEvent, + opts: { source: "message" }, + }); + + expect(prepared).toBeTruthy(); + expect(prepared!.ctxPayload.ThreadStarterBody).toBe("starter from open dm"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("starter from open dm"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("assistant reply"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("dm follow-up"); + expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("current message"); + expect(replies).toHaveBeenCalledTimes(2); + }); + + it("does not apply the owner allowlist to MPIM thread context", async () => { + const replies = vi + .fn() + .mockResolvedValueOnce({ + messages: [{ text: "starter from mpim", user: "U4", ts: "400.000" }], + }) + .mockResolvedValueOnce({ + messages: [ + { text: "starter from mpim", user: "U4", ts: "400.000" }, + { text: "assistant reply", bot_id: "B1", ts: "400.500" }, + { text: "mpim follow-up", user: "U4", ts: "400.800" }, + { text: "current message", user: "U4", ts: "401.000" }, + ], + response_metadata: { next_cursor: "" }, + }); + const storePath = makeTmpStorePath(); + const ctx = createInboundSlackTestContext({ + cfg: { + session: { store: storePath }, + channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } }, + } as OpenClawConfig, + appClient: { conversations: { replies } } as unknown as App["client"], + defaultRequireMention: false, + replyToMode: "all", + }); + ctx.allowFrom = ["u-owner"]; + ctx.resolveUserName = async (id: string) => ({ + name: id === "U4" ? "Evan" : "Owner", + }); + + const prepared = await prepareSlackMessage({ + ctx, + account: createSlackTestAccount({ + replyToMode: "all", + thread: { initialHistoryLimit: 20 }, + }), + message: { + channel: "G400", + channel_type: "mpim", + user: "U4", + text: "current message", + ts: "401.000", + thread_ts: "400.000", + } as SlackMessageEvent, + opts: { source: "message" }, + }); + + expect(prepared).toBeTruthy(); + expect(prepared!.ctxPayload.ThreadStarterBody).toBe("starter from mpim"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("starter from mpim"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("assistant reply"); + expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("mpim follow-up"); + expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("current message"); + expect(replies).toHaveBeenCalledTimes(2); + }); +});
extensions/slack/src/monitor/message-handler/prepare-thread-context.test.ts+146 −0 added@@ -0,0 +1,146 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import type { App } from "@slack/bolt"; +import { resolveEnvelopeFormatOptions } from "openclaw/plugin-sdk/channel-inbound"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import type { SlackMessageEvent } from "../../types.js"; +import { resolveSlackThreadContextData } from "./prepare-thread-context.js"; +import { createInboundSlackTestContext, createSlackTestAccount } from "./prepare.test-helpers.js"; + +describe("resolveSlackThreadContextData", () => { + let fixtureRoot = ""; + let caseId = 0; + + function makeTmpStorePath() { + if (!fixtureRoot) { + throw new Error("fixtureRoot missing"); + } + const dir = path.join(fixtureRoot, `case-${caseId++}`); + fs.mkdirSync(dir); + return { dir, storePath: path.join(dir, "sessions.json") }; + } + + beforeAll(() => { + fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-slack-thread-context-")); + }); + + afterAll(() => { + if (fixtureRoot) { + fs.rmSync(fixtureRoot, { recursive: true, force: true }); + fixtureRoot = ""; + } + }); + + function createThreadContext(params: { replies: unknown }) { + return createInboundSlackTestContext({ + cfg: { + channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } }, + } as OpenClawConfig, + appClient: { conversations: { replies: params.replies } } as App["client"], + defaultRequireMention: false, + replyToMode: "all", + }); + } + + function createThreadMessage(overrides: Partial<SlackMessageEvent> = {}): SlackMessageEvent { + return { + channel: "C123", + channel_type: "channel", + user: "U1", + text: "current message", + ts: "101.000", + thread_ts: "100.000", + ...overrides, + } as SlackMessageEvent; + } + + it("omits non-allowlisted starter text and thread history messages", async () => { + const { storePath } = makeTmpStorePath(); + const replies = vi.fn().mockResolvedValue({ + messages: [ + { text: "starter secret", user: "U2", ts: "100.000" }, + { text: "assistant reply", bot_id: "B1", ts: "100.500" }, + { text: "blocked follow-up", user: "U2", ts: "100.700" }, + { text: "allowed follow-up", user: "U1", ts: "100.800" }, + { text: "current message", user: "U1", ts: "101.000" }, + ], + response_metadata: { next_cursor: "" }, + }); + const ctx = createThreadContext({ replies }); + ctx.resolveUserName = async (id: string) => ({ + name: id === "U1" ? "Alice" : "Mallory", + }); + + const result = await resolveSlackThreadContextData({ + ctx, + account: createSlackTestAccount({ thread: { initialHistoryLimit: 20 } }), + message: createThreadMessage(), + isThreadReply: true, + threadTs: "100.000", + threadStarter: { + text: "starter secret", + userId: "U2", + ts: "100.000", + }, + roomLabel: "#general", + storePath, + sessionKey: "thread-session", + allowFromLower: ["u1"], + allowNameMatching: false, + envelopeOptions: resolveEnvelopeFormatOptions({} as OpenClawConfig), + effectiveDirectMedia: null, + }); + + expect(result.threadStarterBody).toBeUndefined(); + expect(result.threadLabel).toBe("Slack thread #general"); + expect(result.threadHistoryBody).toContain("assistant reply"); + expect(result.threadHistoryBody).toContain("allowed follow-up"); + expect(result.threadHistoryBody).not.toContain("starter secret"); + expect(result.threadHistoryBody).not.toContain("blocked follow-up"); + expect(result.threadHistoryBody).not.toContain("current message"); + expect(replies).toHaveBeenCalledTimes(1); + }); + + it("keeps starter text and history when allowNameMatching authorizes the sender", async () => { + const { storePath } = makeTmpStorePath(); + const replies = vi.fn().mockResolvedValue({ + messages: [ + { text: "starter from Alice", user: "U1", ts: "100.000" }, + { text: "blocked follow-up", user: "U2", ts: "100.700" }, + { text: "current message", user: "U1", ts: "101.000" }, + ], + response_metadata: { next_cursor: "" }, + }); + const ctx = createThreadContext({ replies }); + ctx.resolveUserName = async (id: string) => ({ + name: id === "U1" ? "Alice" : "Mallory", + }); + + const result = await resolveSlackThreadContextData({ + ctx, + account: createSlackTestAccount({ thread: { initialHistoryLimit: 20 } }), + message: createThreadMessage(), + isThreadReply: true, + threadTs: "100.000", + threadStarter: { + text: "starter from Alice", + userId: "U1", + ts: "100.000", + }, + roomLabel: "#general", + storePath, + sessionKey: "thread-session", + allowFromLower: ["alice"], + allowNameMatching: true, + envelopeOptions: resolveEnvelopeFormatOptions({} as OpenClawConfig), + effectiveDirectMedia: null, + }); + + expect(result.threadStarterBody).toBe("starter from Alice"); + expect(result.threadLabel).toContain("starter from Alice"); + expect(result.threadHistoryBody).toContain("starter from Alice"); + expect(result.threadHistoryBody).not.toContain("blocked follow-up"); + }); +});
extensions/slack/src/monitor/message-handler/prepare-thread-context.ts+66 −6 modified@@ -3,6 +3,7 @@ import { readSessionUpdatedAt } from "openclaw/plugin-sdk/config-runtime"; import { logVerbose } from "openclaw/plugin-sdk/runtime-env"; import type { ResolvedSlackAccount } from "../../accounts.js"; import type { SlackMessageEvent } from "../../types.js"; +import { resolveSlackAllowListMatch } from "../allow-list.js"; import type { SlackMonitorContext } from "../context.js"; import { resolveSlackMedia, @@ -19,6 +20,27 @@ export type SlackThreadContextData = { threadStarterMedia: SlackMediaResult[] | null; }; +function isSlackThreadContextSenderAllowed(params: { + allowFromLower: string[]; + allowNameMatching: boolean; + userId?: string; + userName?: string; + botId?: string; +}): boolean { + if (params.allowFromLower.length === 0 || params.botId) { + return true; + } + if (!params.userId) { + return false; + } + return resolveSlackAllowListMatch({ + allowList: params.allowFromLower, + id: params.userId, + name: params.userName, + allowNameMatching: params.allowNameMatching, + }).allowed; +} + export async function resolveSlackThreadContextData(params: { ctx: SlackMonitorContext; account: ResolvedSlackAccount; @@ -29,6 +51,8 @@ export async function resolveSlackThreadContextData(params: { roomLabel: string; storePath: string; sessionKey: string; + allowFromLower: string[]; + allowNameMatching: boolean; envelopeOptions: ReturnType< typeof import("openclaw/plugin-sdk/channel-inbound").resolveEnvelopeFormatOptions >; @@ -51,7 +75,21 @@ export async function resolveSlackThreadContextData(params: { } const starter = params.threadStarter; - if (starter?.text) { + const starterSenderName = + params.allowNameMatching && starter?.userId + ? (await params.ctx.resolveUserName(starter.userId))?.name + : undefined; + const starterAllowed = + !starter || + isSlackThreadContextSenderAllowed({ + allowFromLower: params.allowFromLower, + allowNameMatching: params.allowNameMatching, + userId: starter.userId, + userName: starterSenderName, + botId: starter.botId, + }); + + if (starter?.text && starterAllowed) { threadStarterBody = starter.text; const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80); threadLabel = `Slack thread ${params.roomLabel}${snippet ? `: ${snippet}` : ""}`; @@ -69,6 +107,9 @@ export async function resolveSlackThreadContextData(params: { } else { threadLabel = `Slack thread ${params.roomLabel}`; } + if (starter?.text && !starterAllowed) { + logVerbose("slack: omitted non-allowlisted thread starter from context"); + } const threadInitialHistoryLimit = params.account.config?.thread?.initialHistoryLimit ?? 20; threadSessionPreviousTimestamp = readSessionUpdatedAt({ @@ -101,8 +142,25 @@ export async function resolveSlackThreadContextData(params: { }), ); + const allowedThreadHistory = threadHistory.filter((historyMsg) => { + const msgUser = historyMsg.userId ? userMap.get(historyMsg.userId) : null; + return isSlackThreadContextSenderAllowed({ + allowFromLower: params.allowFromLower, + allowNameMatching: params.allowNameMatching, + userId: historyMsg.userId, + userName: msgUser?.name, + botId: historyMsg.botId, + }); + }); + const omittedHistoryCount = threadHistory.length - allowedThreadHistory.length; + if (omittedHistoryCount > 0) { + logVerbose( + `slack: omitted ${omittedHistoryCount} non-allowlisted thread message(s) from context`, + ); + } + const historyParts: string[] = []; - for (const historyMsg of threadHistory) { + for (const historyMsg of allowedThreadHistory) { const msgUser = historyMsg.userId ? userMap.get(historyMsg.userId) : null; const msgSenderName = msgUser?.name ?? (historyMsg.botId ? `Bot (${historyMsg.botId})` : "Unknown"); @@ -120,10 +178,12 @@ export async function resolveSlackThreadContextData(params: { }), ); } - threadHistoryBody = historyParts.join("\n\n"); - logVerbose( - `slack: populated thread history with ${threadHistory.length} messages for new session`, - ); + if (historyParts.length > 0) { + threadHistoryBody = historyParts.join("\n\n"); + logVerbose( + `slack: populated thread history with ${allowedThreadHistory.length} messages for new session`, + ); + } } }
extensions/slack/src/monitor/message-handler/prepare.ts+12 −0 modified@@ -37,6 +37,7 @@ import { hasSlackThreadParticipation } from "../../sent-thread-cache.js"; import { resolveSlackThreadContext } from "../../threading.js"; import type { SlackMessageEvent } from "../../types.js"; import { + normalizeAllowListLower, normalizeSlackAllowOwnerEntry, resolveSlackAllowListMatch, resolveSlackUserAllowed, @@ -436,6 +437,15 @@ export async function prepareSlackMessage(params: { }).allowed; const channelUsersAllowlistConfigured = isRoom && Array.isArray(channelConfig?.users) && channelConfig.users.length > 0; + const threadContextAllowFromLower = isRoom + ? channelUsersAllowlistConfigured + ? normalizeAllowListLower(channelConfig?.users) + : [] + : isDirectMessage + ? ctx.dmPolicy === "open" + ? [] + : allowFromLower + : []; const channelCommandAuthorized = isRoom && channelUsersAllowlistConfigured ? resolveSlackUserAllowed({ @@ -669,6 +679,8 @@ export async function prepareSlackMessage(params: { roomLabel, storePath, sessionKey, + allowFromLower: threadContextAllowFromLower, + allowNameMatching: ctx.allowNameMatching, envelopeOptions, effectiveDirectMedia, });
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
5- github.com/openclaw/openclaw/commit/ac5bc4fb37becc64a2ec314864cca1565e921f2dnvdPatchWEB
- github.com/advisories/GHSA-qm77-8qjp-4vcmghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-qm77-8qjp-4vcmnvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-41358ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-sender-allowlist-bypass-via-slack-thread-contextnvdThird Party AdvisoryWEB
News mentions
0No linked articles in our index yet.