Root cause
Authentication & access control introduced
Initial release v0.1.0
How AI contributed: Direct introductionPublished Apr 2, 2026 in
openclaw/openclawAll 35 findings in this repository
Summary Feishu thread history and quoted messages bypass sender allowlist Current Maintainer Triage · Status: open · Normalized severity: medium · Assessment: Real in shipped v2026.3.28 Feishu because fetched quoted/root/thread context bypasses sender allowlists, and SECURITY.md does not exempt remote sender-allowlist bypasses. Affected Packages / Versions · Package: openclaw (npm) · Latest published npm version: 2026.3.31 · Vulnerable version range: <=2026.3.28 · Patched versions: >= 2026.3.31 · First stable tag containing the fix: v2026.3.31 Fix Commit(s) · f45e5a6569aab1d58cc6de25b19f1dc4c8779b85 — 2026-03-31T19:43:54+09:00 Release Process Note · The fix is already present in released version 2026.3.31. · This draft looks ready for final maintainer disposition or publication, not additional code-fix work. OpenClaw thanks @AntAISecurityLab for reporting.
Two AI-written branches independently fetched remote Feishu context without applying the effective sender allowlist: upstream 4286755f introduced quoted-message fetching, while downstream 8a607d75 introduced topic-thread history.
Root cause
Initial release v0.1.0
Fix
fix(feishu): filter fetched group thread context (#58237)
Case facts
+ }+ }+ }++ try {+ const core = getFeishuRuntime();++ const feishuFrom = isGroup ? `feishu:group:${ctx.chatId}` : `feishu:${ctx.senderOpenId}`;+ const feishuTo = isGroup ? `chat:${ctx.chatId}` : `user:${ctx.senderOpenId}`;++ const route = core.channel.routing.resolveAgentRoute({+ cfg,+ channel: "feishu",+ peer: {+ kind: isGroup ? "group" : "dm",+ id: isGroup ? ctx.chatId : ctx.senderOpenId,+ },+ });++ const preview = ctx.content.replace(/\s+/g, " ").slice(0, 160);+ const inboundLabel = isGroup+ ? `Feishu message in group ${ctx.chatId}`+ : `Feishu DM from ${ctx.senderOpenId}`;++ core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, {+ sessionKey: route.sessionKey,+ contextKey: `feishu:message:${ctx.chatId}:${ctx.messageId}`,+ });++ // Fetch quoted/replied message content if parentId exists+ let quotedContent: string | undefined;+ if (ctx.parentId) {+ try {+ const quotedMsg = await getMessageFeishu({ cfg, messageId: ctx.parentId });+ if (quotedMsg) {+ quotedContent = quotedMsg.content;+ log(`feishu: fetched quoted message: ${quotedContent?.slice(0, 100)}`);+ }+ } catch (err) {+ log(`feishu: failed to fetch quoted message: ${String(err)}`);+ }+ }++ const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);++ // Build message body with quoted content if available+ let messageBody = ctx.content;+ if (quotedContent) {+ messageBody = `[Replying to: "${quotedContent}"]\n\n${ctx.content}`;+ }++ const body = core.channel.reply.formatAgentEnvelope({+ channel: "Feishu",+ from: isGroup ? ctx.chatId : ctx.senderOpenId,+ timestamp: new Date(),+ envelope: envelopeOptions,+ body: messageBody,+ });++ let combinedBody = body;+ const historyKey = isGroup ? ctx.chatId : undefined;++ if (isGroup && historyKey && chatHistories) {+ combinedBody = buildPendingHistoryContextFromMap({+ historyMap: chatHistories,+ historyKey,+ limit: historyLimit,+ currentMessage: combinedBody,+ formatEntry: (entry) =>+ core.channel.reply.formatAgentEnvelope({+ channel: "Feishu",+ from: ctx.chatId,+ timestamp: entry.timestamp,+ body: `${entry.sender}: ${entry.body}`,+ envelope: envelopeOptions,+ }),+ });+ }++ const ctxPayload = core.channel.reply.finalizeInboundContext({+ Body: combinedBody,+ RawBody: ctx.content,+ CommandBody: ctx.content,+ From: feishuFrom,+ To: feishuTo,+ SessionKey: route.sessionKey,+ AccountId: route.accountId,+ ChatType: isGroup ? "group" : "direct",+ GroupSubject: isGroup ? ctx.chatId : undefined,+ SenderName: ctx.senderOpenId, export { toMessageResourceType } from "./bot-content.js";@@ -218,6 +218,49 @@ export function buildFeishuAgentBody(params: { return messageBody; } +function shouldIncludeFetchedGroupContextMessage(params: {+ isGroup: boolean;+ allowFrom: Array<string | number>;+ senderId?: string;+ senderType?: string;+}): boolean {+ if (!params.isGroup || params.allowFrom.length === 0) {+ return true;+ }+ if (params.senderType === "app") {+ return true;+ }+ const senderId = params.senderId?.trim();+ if (!senderId) {+ return false;+ }+ return isFeishuGroupAllowed({+ groupPolicy: "allowlist",+ allowFrom: params.allowFrom,+ senderId,+ senderName: undefined,+ });+}++function filterFetchedGroupContextMessages<+ T extends Pick<FeishuMessageInfo, "senderId" | "senderType">,+>(+ messages: readonly T[],+ params: {+ isGroup: boolean;+ allowFrom: Array<string | number>;+ },+): T[] {+ return messages.filter((message) =>+ shouldIncludeFetchedGroupContextMessage({+ isGroup: params.isGroup,+ allowFrom: params.allowFrom,+ senderId: message.senderId,+ senderType: message.senderType,+ }),+ );+}+ export async function handleFeishuMessage(params: { cfg: ClawdbotConfig; event: FeishuMessageEvent;@@ -337,6 +380,11 @@ export async function handleFeishuMessage(params: { const groupConfig = isGroup ? resolveFeishuGroupConfig({ cfg: feishuCfg, groupId: ctx.chatId }) : undefined;+ const effectiveGroupSenderAllowFrom = isGroup+ ? (groupConfig?.allowFrom?.length ?? 0) > 0+ ? (groupConfig?.allowFrom ?? [])+ : (feishuCfg?.groupSenderAllowFrom ?? [])+ : []; const groupSession = isGroup ? resolveFeishuGroupSession({ chatId: ctx.chatId,@@ -402,14 +450,10 @@ export async function handleFeishuMessage(params: { } // Sender-level allowlist: per-group allowFrom takes precedence, then global groupSenderAllowFrom- const perGroupSenderAllowFrom = groupConfig?.allowFrom ?? [];- const globalSenderAllowFrom = feishuCfg?.groupSenderAllowFrom ?? [];- const effectiveSenderAllowFrom =- perGroupSenderAllowFrom.length > 0 ? perGroupSenderAllowFrom : globalSenderAllowFrom;- if (effectiveSenderAllowFrom.length > 0) {+ if (effectiveGroupSenderAllowFrom.length > 0) { const senderAllowed = isFeishuGroupAllowed({ groupPolicy: "allowlist",- allowFrom: effectiveSenderAllowFrom,+ allowFrom: effectiveGroupSenderAllowFrom, senderId: ctx.senderOpenId, senderIds: [senderUserId], senderName: ctx.senderName,@@ -691,11 +735,23 @@ export async function handleFeishuMessage(params: { messageId: ctx.parentId, accountId: account.accountId, });- if (quotedMessageInfo) {+ if (+ quotedMessageInfo &&+ shouldIncludeFetchedGroupContextMessage({+ isGroup,AI-assisted change 024abc04a5fd29b2afa2a2909b25f86b56c092b1b13ae8f33c6409b58dc6e26e · Fix 3c1e85304772c0abe7b987178883d5a2fef6e3ba4f03c07288057457f4199584