fix(feishu): restore quoted card content by avoiding lossy interactive placeholder#32742
fix(feishu): restore quoted card content by avoiding lossy interactive placeholder#32742KimGLee wants to merge 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes a regression introduced in Key changes:
Minor observation: There is no test for the edge case where Confidence Score: 4/5
Last reviewed commit: 7df0e43 |
2ba1cf1 to
efc0d79
Compare
🔒 Aisle Security AnalysisWe found 3 potential security issue(s) in this PR:
1. 🟠 Feishu interactive-card raw JSON fallback can leak sensitive data to logs/LLM context
Description
This is risky because Feishu interactive cards commonly include:
Once returned as
Vulnerable code (new behavior): if (msgType === "interactive") {
const extracted = parseInteractiveCardContent(parsed);
return extracted ?? rawContent;
}This behavior is also codified in the new test RecommendationDo not fall back to returning the full raw interactive-card JSON as message text. Safer options:
if (msgType === "interactive") {
const extracted = parseInteractiveCardContent(parsed);
return extracted ?? "[Interactive Card]";
}
Additionally, consider storing raw card JSON in a separate field (not 2. 🟡 Unbounded interactive card JSON returned as message content (resource exhaustion risk)
Description
Vulnerable code: if (msgType === "interactive") {
const extracted = parseInteractiveCardContent(parsed);
return extracted ?? rawContent;
}RecommendationApply a size cap / truncation (and ideally avoid embedding raw card JSON into prompts at all). Options:
if (msgType === "interactive") {
const extracted = parseInteractiveCardContent(parsed);
return extracted ?? "[Interactive Card]";
}
const MAX_LEN = 2000;
if (msgType === "interactive") {
const extracted = parseInteractiveCardContent(parsed);
if (extracted) return extracted;
const truncated = rawContent.length > MAX_LEN
? rawContent.slice(0, MAX_LEN) + `… [truncated ${rawContent.length - MAX_LEN} chars]`
: rawContent;
return truncated;
}Also consider keeping raw card JSON in a separate field that is not fed into LLM prompts or persisted in chat history. 3. 🟡 Untrusted Feishu interactive card JSON returned as message content (prompt/log injection risk)
DescriptionThe Feishu message parser now falls back to returning the raw interactive card JSON payload when it cannot extract human-readable text. This is risky because
If an attacker can cause an Vulnerable code: if (msgType === "interactive") {
const extracted = parseInteractiveCardContent(parsed);
return extracted ?? rawContent;
}RecommendationDo not return raw interactive JSON as a fallback. Prefer a safe placeholder and/or a tightly-scoped, sanitized summary. Option A (restore safe placeholder fallback): if (msgType === "interactive") {
const extracted = parseInteractiveCardContent(parsed);
return extracted ?? "[Interactive Card]";
}Option B (sanitized JSON preview with strict limits): function safePreview(s: string, max = 500): string {
// strip control chars + collapse whitespace to prevent log/prompt forging
return s.replace(/[\u0000-\u001F\u007F]/g, " ").replace(/\s+/g, " ").slice(0, max);
}
if (msgType === "interactive") {
const extracted = parseInteractiveCardContent(parsed);
return extracted ?? `[Interactive Card: ${safePreview(rawContent)}]`;
}Additionally, consider:
Analyzed PR: #32742 at commit Last updated on: 2026-03-22T05:38:05Z |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cd6bc7215c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| content, | ||
| contentType: msgType, | ||
| createTime: item.create_time ? parseInt(String(item.create_time), 10) : undefined, | ||
| }; |
There was a problem hiding this comment.
Preserve threadId when returning fetched Feishu messages
getMessageFeishu now returns a message object without threadId, even though downstream thread bootstrap relies on rootMsg?.threadId when the inbound event lacks ctx.threadId (extensions/feishu/src/bot.ts:1387). In topic sessions where Feishu does not include thread_id on the event but does provide it on the root message fetch, this change makes feishuThreadId stay empty, so thread history loading is skipped and agents lose prior thread context.
Useful? React with 👍 / 👎.
| const candidate = parsed as { elements?: unknown }; | ||
| if (!Array.isArray(candidate.elements)) { | ||
| return undefined; |
There was a problem hiding this comment.
Keep schema 2.0 card parsing in interactive quote extraction
The interactive-card parser now only checks top-level elements and no longer reads body.elements, which is the structure produced by schema 2.0 cards (for example from buildMarkdownCard). As a result, standard quoted interactive cards stop yielding extracted text and always fall back to raw JSON payloads, reducing message quality and context compared to prior behavior.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: af7653e828
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (currentMessageId && item.message_id === currentMessageId) continue; | ||
| if (rootMessageId && item.message_id === rootMessageId) continue; | ||
|
|
||
| const parsed = parseFeishuMessageItem(item); |
There was a problem hiding this comment.
Reintroduce parseFeishuMessageItem before thread listing
listFeishuThreadMessages now calls parseFeishuMessageItem, but that helper was removed in this commit, so any non-empty thread will throw a ReferenceError at runtime instead of returning history. This path is used during Feishu thread-context bootstrap, so threaded conversations lose prior context when message listing runs.
Useful? React with 👍 / 👎.
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "name": "@openclaw/ollama-provider", | |||
| "version": "2026.3.14", | |||
| "version": "2026.3.11", | |||
There was a problem hiding this comment.
Revert accidental provider version downgrade
This change rolls the built-in provider manifest version backward (2026.3.14 → 2026.3.11), and the same downgrade is present in the sibling sglang and vllm provider manifests in this commit. Plugin discovery exposes manifest versions directly, so these providers will report older metadata than the current CLI release, which can confuse version-based update/audit flows and release consistency.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64177c889a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| */ | ||
| export async function sendStructuredCardFeishu(params: { | ||
| export async function sendMarkdownCardFeishu(params: { |
There was a problem hiding this comment.
Restore structured-card exports from send.ts
send.ts no longer exports sendStructuredCardFeishu, resolveFeishuCardTemplate, or CardHeaderConfig, but other Feishu runtime modules still import them (for example outbound and streaming card paths). In ESM this is a hard module-link error (does not provide an export named ...), so loading the Feishu runtime fails before handling messages, which breaks Feishu channel actions at runtime.
Useful? React with 👍 / 👎.
| if (shouldFallbackFromReplyTarget(response)) { | ||
| return sendFallbackDirect(client, directParams, "Feishu send failed"); | ||
| } |
There was a problem hiding this comment.
Keep thread replies from falling back to top-level sends
When replyInThread is true and Feishu reports the parent message as withdrawn/not found, this branch now falls back to message.create instead of failing, which posts a top-level message outside the topic thread. That leaks thread-only replies into the main chat and breaks thread continuity; the same regression pattern appears in both text and card send paths.
Useful? React with 👍 / 👎.
| const messageText = getFeishuRuntime().channel.text.convertMarkdownTables(text ?? "", tableMode); | ||
|
|
||
| const { content, msgType } = buildFeishuPostMessagePayload({ messageText }); | ||
|
|
||
| const response = await client.im.message.update({ | ||
| path: { message_id: messageId }, | ||
| data: { | ||
| msg_type: msgType, | ||
| content, |
There was a problem hiding this comment.
Preserve interactive card editing in editMessageFeishu
This rewrite removed the card-edit branch and now always derives a post payload from text ?? "", so callers that send card-only edits end up overwriting the message with an empty/incorrect post instead of patching interactive content. Since Feishu edit flows still pass optional card payloads, card updates will silently stop working and can corrupt edited message content.
Useful? React with 👍 / 👎.
64177c8 to
f6d5ea7
Compare
f6d5ea7 to
00c6634
Compare
|
Closing this PR because the current mainline Feishu direction has moved on: the official plugin now covers streaming cards and v2 card support, so this older fix path is no longer necessary to keep advancing. If related gaps remain in the future, they should be revisited against the current Feishu implementation rather than continued from this branch. |
Summary
Fix Feishu quoted interactive-card regression by preserving raw quoted payload when card-text extraction cannot decode the card structure.
interactivecard element shapeselementsmissing/unsupported), fallback to raw quoted payload instead of hardcoded"[Interactive Card]"Fixes #32712.
Root cause analysis (version diff)
Behavior in
v2026.2.26(worked)In
extensions/feishu/src/send.ts, quoted-message parsing for non-text messages effectively preserved useful raw content. For quoted interactive cards, OpenClaw still had structured payload available to downstream reasoning (at minimum raw JSON content), so quoting a card in Feishu groups remained usable.Behavior introduced by
v2026.3.1(regression)v2026.3.1introduced stricter quoted parsing inextensions/feishu/src/send.ts(commit6ea3a47da,fix(feishu): harden routing, parsing, and media delivery):parseQuotedMessageContent()now routesmsg_type === "interactive"intoparseInteractiveCardContent()parseInteractiveCardContent()only extracts text from a narrow shape (elements[]withmarkdown/div.text.content)"[Interactive Card]"That means quoted card payload gets flattened to a placeholder, which is exactly the user-visible regression:
[Replying to: "[Interactive Card]"]So the regression is not failure to fetch quoted message ID; it is over-aggressive lossy normalization after fetch.
What this PR changes
Before
"[Interactive Card]"(payload lost)After
body.contentJSON string (payload preserved)This restores practical behavior from pre-regression versions while keeping improved parsing for recognized card structures.
Tests
Added regression coverage in
extensions/feishu/src/send.test.ts:falls back to raw interactive payload when card structure is unknownLocal run:
pnpm exec vitest run extensions/feishu/src/send.test.tsPassed: 5/5 tests.