VYPR
Medium severity5.3NVD Advisory· Published Apr 10, 2026· Updated Apr 13, 2026

CVE-2026-35664

CVE-2026-35664

Description

OpenClaw before 2026.3.25 contains an authentication bypass vulnerability in raw card send surface that allows unpaired recipients to mint legacy callback payloads. Attackers can send raw card commands to bypass DM pairing restrictions and reach callback handling without proper authorization.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.3.282026.3.28

Affected products

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

Patches

1
81c45976db53

Feishu: reject legacy raw card command payloads (#55130)

https://github.com/openclaw/openclawJacob TomlinsonMar 26, 2026via ghsa
2 files changed · +182 0
  • extensions/feishu/src/channel.test.ts+148 0 modified
    @@ -1,5 +1,6 @@
     import { beforeEach, describe, expect, it, vi } from "vitest";
     import type { OpenClawConfig } from "../runtime-api.js";
    +import { createFeishuCardInteractionEnvelope } from "./card-interaction.js";
     import { looksLikeFeishuId, normalizeFeishuTarget, resolveReceiveIdType } from "./targets.js";
     
     const probeFeishuMock = vi.hoisted(() => vi.fn());
    @@ -206,6 +207,153 @@ describe("feishuPlugin actions", () => {
         expect(result?.details).toMatchObject({ ok: true, messageId: "om_card", chatId: "oc_group_1" });
       });
     
    +  it("allows structured card button payloads", async () => {
    +    sendCardFeishuMock.mockResolvedValueOnce({ messageId: "om_card", chatId: "oc_group_1" });
    +    const card = {
    +      schema: "2.0",
    +      body: {
    +        elements: [
    +          {
    +            tag: "action",
    +            actions: [
    +              {
    +                tag: "button",
    +                text: { tag: "plain_text", content: "Run /new" },
    +                value: createFeishuCardInteractionEnvelope({
    +                  k: "quick",
    +                  a: "feishu.quick_actions.help",
    +                  q: "/help",
    +                  c: { u: "u123", h: "oc_group_1", t: "group", e: Date.now() + 60_000 },
    +                }),
    +              },
    +            ],
    +          },
    +        ],
    +      },
    +    };
    +
    +    await feishuPlugin.actions?.handleAction?.({
    +      action: "send",
    +      params: { to: "chat:oc_group_1", card },
    +      cfg,
    +      accountId: undefined,
    +      toolContext: {},
    +    } as never);
    +
    +    expect(sendCardFeishuMock).toHaveBeenCalledWith(
    +      expect.objectContaining({
    +        card,
    +      }),
    +    );
    +  });
    +
    +  it("rejects raw legacy card command payloads", async () => {
    +    const legacyCard = {
    +      schema: "2.0",
    +      body: {
    +        elements: [
    +          {
    +            tag: "action",
    +            actions: [
    +              {
    +                tag: "button",
    +                text: { tag: "plain_text", content: "Run /new" },
    +                value: { command: "/new" },
    +              },
    +            ],
    +          },
    +        ],
    +      },
    +    };
    +
    +    await expect(
    +      feishuPlugin.actions?.handleAction?.({
    +        action: "send",
    +        params: { to: "chat:oc_group_1", card: legacyCard },
    +        cfg,
    +        accountId: undefined,
    +        toolContext: {},
    +      } as never),
    +    ).rejects.toThrow(
    +      "Feishu card buttons that trigger text or commands must use structured interaction envelopes.",
    +    );
    +    expect(sendCardFeishuMock).not.toHaveBeenCalled();
    +  });
    +
    +  it("rejects raw legacy card text payloads", async () => {
    +    const legacyCard = {
    +      schema: "2.0",
    +      body: {
    +        elements: [
    +          {
    +            tag: "action",
    +            actions: [
    +              {
    +                tag: "button",
    +                text: { tag: "plain_text", content: "Run /new" },
    +                value: { text: "/new" },
    +              },
    +            ],
    +          },
    +        ],
    +      },
    +    };
    +
    +    await expect(
    +      feishuPlugin.actions?.handleAction?.({
    +        action: "send",
    +        params: { to: "chat:oc_group_1", card: legacyCard },
    +        cfg,
    +        accountId: undefined,
    +        toolContext: {},
    +      } as never),
    +    ).rejects.toThrow(
    +      "Feishu card buttons that trigger text or commands must use structured interaction envelopes.",
    +    );
    +    expect(sendCardFeishuMock).not.toHaveBeenCalled();
    +  });
    +
    +  it("allows non-button controls to carry text metadata values", async () => {
    +    sendCardFeishuMock.mockResolvedValueOnce({ messageId: "om_card", chatId: "oc_group_1" });
    +    const card = {
    +      schema: "2.0",
    +      body: {
    +        elements: [
    +          {
    +            tag: "action",
    +            actions: [
    +              {
    +                tag: "select_static",
    +                placeholder: { tag: "plain_text", content: "Pick one" },
    +                value: { text: "display-only metadata" },
    +                options: [
    +                  {
    +                    text: { tag: "plain_text", content: "Option A" },
    +                    value: "a",
    +                  },
    +                ],
    +              },
    +            ],
    +          },
    +        ],
    +      },
    +    };
    +
    +    await feishuPlugin.actions?.handleAction?.({
    +      action: "send",
    +      params: { to: "chat:oc_group_1", card },
    +      cfg,
    +      accountId: undefined,
    +      toolContext: {},
    +    } as never);
    +
    +    expect(sendCardFeishuMock).toHaveBeenCalledWith(
    +      expect.objectContaining({
    +        card,
    +      }),
    +    );
    +  });
    +
       it("sends media through the outbound adapter", async () => {
         feishuOutboundSendMediaMock.mockResolvedValueOnce({
           channel: "feishu",
    
  • extensions/feishu/src/channel.ts+34 0 modified
    @@ -40,6 +40,7 @@ import {
       listEnabledFeishuAccounts,
       resolveDefaultFeishuAccountId,
     } from "./accounts.js";
    +import { FEISHU_CARD_INTERACTION_VERSION } from "./card-interaction.js";
     import { createFeishuClient } from "./client.js";
     import { FeishuConfigSchema } from "./config-schema.js";
     import { parseFeishuConversationId } from "./conversation-id.js";
    @@ -60,6 +61,34 @@ function readFeishuMediaParam(params: Record<string, unknown>): string | undefin
       return media.trim() ? media : undefined;
     }
     
    +function isRecord(value: unknown): value is Record<string, unknown> {
    +  return typeof value === "object" && value !== null;
    +}
    +
    +function hasLegacyFeishuCardCommandValue(actionValue: unknown): boolean {
    +  return (
    +    isRecord(actionValue) &&
    +    actionValue.oc !== FEISHU_CARD_INTERACTION_VERSION &&
    +    (Boolean(typeof actionValue.command === "string" && actionValue.command.trim()) ||
    +      Boolean(typeof actionValue.text === "string" && actionValue.text.trim()))
    +  );
    +}
    +
    +function containsLegacyFeishuCardCommandValue(node: unknown): boolean {
    +  if (Array.isArray(node)) {
    +    return node.some((item) => containsLegacyFeishuCardCommandValue(item));
    +  }
    +  if (!isRecord(node)) {
    +    return false;
    +  }
    +
    +  if (node.tag === "button" && hasLegacyFeishuCardCommandValue(node.value)) {
    +    return true;
    +  }
    +
    +  return Object.values(node).some((value) => containsLegacyFeishuCardCommandValue(value));
    +}
    +
     const meta: ChannelMeta = {
       id: "feishu",
       label: "Feishu",
    @@ -526,6 +555,11 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount, FeishuProbeResul
                 const sendMedia = maybeSendMedia;
                 let result;
                 if (card) {
    +              if (containsLegacyFeishuCardCommandValue(card)) {
    +                throw new Error(
    +                  "Feishu card buttons that trigger text or commands must use structured interaction envelopes.",
    +                );
    +              }
                   result = await runtime.sendCardFeishu({
                     cfg: ctx.cfg,
                     to,
    

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

4

News mentions

0

No linked articles in our index yet.