Skip to content

fix(feishu): repair interactive card content extraction#72397

Merged
vincentkoc merged 1 commit intomainfrom
clownfish/ghcrawl-199244-agentic-merge
Apr 26, 2026
Merged

fix(feishu): repair interactive card content extraction#72397
vincentkoc merged 1 commit intomainfrom
clownfish/ghcrawl-199244-agentic-merge

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

Repair Feishu interactive-card content extraction for quoted/replied cards while keeping the patch limited to the existing parser and tests.

This carries forward the narrow work from #38776 and adds the review-bot-requested fallbacks for empty elements / body.elements / i18n_elements, locale fallback when the preferred locale has no extractable text, non-string template variables, and focused post-format fallback coverage from #60383.

Credit

Validation

  • pnpm check:changed

Notes

This does not close the broader inbound interactive-card parsing work tracked by #41609 and #56795.

ProjectClownfish replacement details:

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 26, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Sensitive data exposure via interactive card template variable expansion
1. 🟡 Sensitive data exposure via interactive card template variable expansion
Property Value
Severity Medium
CWE CWE-532
Location extensions/feishu/src/send.ts:214-221

Description

parseInteractiveCardContent() now expands Feishu interactive-card template_variable/template_variables into the extracted text (supports ${var} and {{var}}).

If card template variables contain secrets/PII (tokens, internal IDs, user data), the expansion increases the chance of leaking sensitive values because the extracted text is subsequently used outside Feishu:

  • It is logged in plaintext when fetching quoted messages (first 100 chars):
    • extensions/feishu/src/bot.ts:816 logs quotedMessageInfo.content, which comes from parseFeishuMessageContent()parseInteractiveCardContent().
  • It is also forwarded into agent context (thread history, quoting, etc.), potentially storing/propagating secret values to downstream systems.

Vulnerable code (template expansion):

return text.replace(/\$\{([A-Za-z0-9_.-]+)\}|\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (match, a, b) => {
  const variableName = typeof a === "string" ? a : b;
  return variables.get(variableName) ?? match;
});

Recommendation

Avoid expanding template variables when producing agent/log-facing plaintext, or strictly limit what can be expanded.

Options:

  1. Do not expand variables for extracted plaintext (safest):
function applyCardTemplateVariables(text: string, _variables: Map<string, string>): string {
  return text; // keep placeholders
}
  1. Allowlist variables that are known-safe (e.g., presentation-only fields), and leave others unexpanded:
const SAFE_VARS = new Set(["user_name", "title"]);
return text.replace(/\$\{([A-Za-z0-9_.-]+)\}|\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (match, a, b) => {
  const name = (typeof a === "string" ? a : b) ?? "";
  if (!SAFE_VARS.has(name)) return match;
  return variables.get(name) ?? match;
});
  1. Redact before logging/forwarding: if you keep expansion, ensure all logs and any persisted/forwarded context go through a redaction layer.

Additionally, consider adding a config flag (default: off) controlling variable expansion for interactive-card text extraction.


Analyzed PR: #72397 at commit 226249d

Last updated on: 2026-04-26T22:28:51Z

@vincentkoc vincentkoc merged commit c6cf370 into main Apr 26, 2026
11 of 12 checks passed
@vincentkoc vincentkoc deleted the clownfish/ghcrawl-199244-agentic-merge branch April 26, 2026 22:26
@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: M maintainer Maintainer-authored PR labels Apr 26, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR refactors parseInteractiveCardContent in the Feishu extension to handle schema 1.0/2.0 element arrays, i18n_elements locale fall-through, template variable substitution (string, number, boolean; objects are silently dropped), and a post-format fallback. It also adds md/lark_md tag support to post.ts's renderElement switch and ships two focused integration tests.

Confidence Score: 4/5

Safe to merge; no critical defects found in the changed paths.

Only P2-level findings: a minor JSON round-trip inefficiency in the post-fallback and a non-deterministic locale-selection order for i18n_elements. Both are edge-case concerns rather than present defects for the tested scenarios.

extensions/feishu/src/send.ts — parseInteractivePostFallback and readInteractiveElementArrays locale ordering

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/feishu/src/send.ts
Line: 285

Comment:
**Unnecessary JSON round-trip in post fallback**

`parsed` was already deserialized by `parseFeishuMessageContent` before reaching here; `JSON.stringify(parsed)` re-serializes it only so `parsePostContent` can re-parse it. This is wasteful and introduces a subtle risk if `parsed` contains non-JSON-serializable values (e.g. `bigint` from `normalizeCardTemplateVariable`, though only as template variable values, not the card structure itself). Consider exposing an overload or a shared helper that accepts the already-parsed object instead.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/feishu/src/send.ts
Line: 257-281

Comment:
**`i18n_elements` locale iteration order is insertion-order-dependent**

`Object.values(candidate)` iterates `i18n_elements` locales in JSON insertion order (V8 preserves string-key insertion order, so this is reliable within Node.js). However, the first locale with non-empty elements wins — there is no preference for a user/bot locale or any explicit priority (e.g. `en_us` before `zh_cn`). If a card carries multiple locales with different content, the result depends on whichever locale was serialized first by the sender. This may produce surprising output when a preferred locale is available but not first in the object.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(feishu): repair interactive card con..." | Re-trigger Greptile

}

function parseInteractivePostFallback(parsed: unknown): string | undefined {
const textContent = parsePostContent(JSON.stringify(parsed)).textContent.trim();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unnecessary JSON round-trip in post fallback

parsed was already deserialized by parseFeishuMessageContent before reaching here; JSON.stringify(parsed) re-serializes it only so parsePostContent can re-parse it. This is wasteful and introduces a subtle risk if parsed contains non-JSON-serializable values (e.g. bigint from normalizeCardTemplateVariable, though only as template variable values, not the card structure itself). Consider exposing an overload or a shared helper that accepts the already-parsed object instead.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/feishu/src/send.ts
Line: 285

Comment:
**Unnecessary JSON round-trip in post fallback**

`parsed` was already deserialized by `parseFeishuMessageContent` before reaching here; `JSON.stringify(parsed)` re-serializes it only so `parsePostContent` can re-parse it. This is wasteful and introduces a subtle risk if `parsed` contains non-JSON-serializable values (e.g. `bigint` from `normalizeCardTemplateVariable`, though only as template variable values, not the card structure itself). Consider exposing an overload or a shared helper that accepts the already-parsed object instead.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +257 to +281
return texts.join("\n").trim();
}

function readInteractiveElementArrays(parsed: Record<string, unknown>): unknown[][] {
const body = isRecord(parsed.body) ? parsed.body : undefined;
const elementArrays: unknown[][] = [];

for (const candidate of [parsed.elements, body?.elements]) {
if (Array.isArray(candidate)) {
elementArrays.push(candidate);
}
}

for (const candidate of [parsed.i18n_elements, body?.i18n_elements]) {
if (!isRecord(candidate)) {
continue;
}
if (item.tag === "markdown" && typeof item.content === "string") {
texts.push(item.content);
for (const localeElements of Object.values(candidate)) {
if (Array.isArray(localeElements)) {
elementArrays.push(localeElements);
}
}
}

return elementArrays;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 i18n_elements locale iteration order is insertion-order-dependent

Object.values(candidate) iterates i18n_elements locales in JSON insertion order (V8 preserves string-key insertion order, so this is reliable within Node.js). However, the first locale with non-empty elements wins — there is no preference for a user/bot locale or any explicit priority (e.g. en_us before zh_cn). If a card carries multiple locales with different content, the result depends on whichever locale was serialized first by the sender. This may produce surprising output when a preferred locale is available but not first in the object.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/feishu/src/send.ts
Line: 257-281

Comment:
**`i18n_elements` locale iteration order is insertion-order-dependent**

`Object.values(candidate)` iterates `i18n_elements` locales in JSON insertion order (V8 preserves string-key insertion order, so this is reliable within Node.js). However, the first locale with non-empty elements wins — there is no preference for a user/bot locale or any explicit priority (e.g. `en_us` before `zh_cn`). If a card carries multiple locales with different content, the result depends on whichever locale was serialized first by the sender. This may produce surprising output when a preferred locale is available but not first in the object.

How can I resolve this? If you propose a fix, please make it concise.

@vincentkoc vincentkoc added the clawsweeper Tracked by ClawSweeper automation label Apr 27, 2026
bminicore pushed a commit to bminicore/openclaw-fork that referenced this pull request Apr 27, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu clawsweeper Tracked by ClawSweeper automation maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant