fix(feishu): neutralize prompt injection in comment-card timeline - #66749
fix(feishu): neutralize prompt injection in comment-card timeline#66749Frowtek wants to merge 1 commit into
Conversation
`build_local_comment_prompt` / `build_whole_comment_prompt` render the comment-card timeline as `[user_id] text` lines joined with newlines into the prompt `_run_comment_agent` hands to the model. The comment `text` is untrusted — any collaborator on the document can write a comment, and `_extract_reply_text` preserves its embedded newlines — yet it was interpolated raw. A comment such as "looks good\n## SYSTEM: ..." therefore breaks out of its timeline line and poses as a fresh markdown section (a fake "## SYSTEM" / "## Override" heading) inside the prompt. This is the same indirect-prompt-injection vector already closed for the sibling untrusted sinks: the sender-name prefix (`neutralize_untrusted_inline_text`), the reply quote, and the Slack/Discord thread-context backfills. The Feishu comment-card timeline was the missed sink. Route both builders through a small `_timeline_entry_line` helper that flattens each entry with `neutralize_untrusted_inline_text`. `max_chars=0` defers length capping to the existing `_truncate`, so the per-comment prompt limit is unchanged and a well-behaved comment renders as before. Adds a regression test covering both builders.
|
Thanks for the focused security fix. The premise is verified on current HEAD: Automated hermes-sweeper review. |
GottZ
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
Summary
Six PRs are associated with this complex, but only #66749 addresses the reported prompt-injection cause by neutralizing untrusted Feishu timeline text in both prompt builders. #58970, #61140, #61269, #61717, and #64864 instead address a separate asyncio.gather exception-isolation problem and do not close the security gap fixed by #66749.
Related pull requests
- #58970 [closed]
related— (+11/-2) — unrelated reliability fix: adds per-fetch exception fallback to the Feishu comment handler, plus an unrelated QQBotgatherchange; it does not neutralize untrusted timeline text. It remains relevant as an early implementation of the gather fix, but was closed after duplicating earlier Feishu work and mixing scopes. - #61140 [closed]
related— (+9/-1) — superseded reliability fix: isolates Feishu metadata and comment-fetch exceptions with empty-dict fallbacks, but does not address timeline prompt injection. It was explicitly closed as superseded by #61269. - #61269 [closed]
related— (+9/-1) — duplicate reliability fix: reproduces #61140's single-filereturn_exceptions=Truehandling and leaves raw timeline interpolation unchanged. It remains relevant as the clean resubmission, but was closed as a duplicate. - #61717 [closed]
related— (+119/-1) — tested but unrelated reliability fix: adds exception isolation and handler-level tests for each parallel fetch failure, while leaving the prompt-injection sink untouched. Despite the keep_open review on #61717, its diff addresses a separate gather failure mode rather than #66749's security cause, so it should not replace the target PR; the review's requested regression coverage is present in the shown diff. - #64864 [closed]
related— (+137/-1) — duplicate reliability fix with regression coverage: implements the same gather fallbacks and tests a locally reproduced gather pattern, but does not modify either vulnerable timeline builder. It remains relevant as a later tested iteration of #61717/#61269, not as an alternative to #66749. - #66749
related— (+71/-4) — targeted security fix: routes entries from both local and whole-comment timelines through the established inline-text neutralizer, preventing embedded newlines from creating injected prompt sections, and tests both vulnerable paths. This matches the keep_open review on #66749, which verified the sink and the established mitigation on the reviewed commit.
Duplicates
#61140 and #61269 are effectively identical gather fixes; #61717 and #64864 implement the same underlying gather exception-isolation change with added tests. #58970 overlaps that group on Feishu while also bundling an unrelated QQBot change. None is a duplicate of #66749.
Suggested consolidation
Merge #66749 because it is the only PR whose diff directly removes the Feishu comment-timeline prompt-injection vector and covers both prompt builders with focused tests. Keep #58970, #61140, #61269, #61717, and #64864 closed for this consolidation: they can be treated as duplicates or iterations of the separate gather-reliability fix, not as substitutes for #66749.
Cross-PR triage: Reviewed 6 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 23 kB of PR diffs, 9 kB of issue/PR text, 7 kB of discussion (18 comments), 4 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.
|
suggesting changes The shared timeline renderer now collapses collaborator-controlled comment text before it is joined into either prompt. A separate collaborator-controlled input remains: matching
Security evidence:
Review setup: I reviewed a run-owned local rebase or patch replay against current GitHub Not checked:
Signed: GPT-5.6-luna-max in Codex |
PR #66749 — fix(feishu): neutralize prompt injection in comment-card timeline No blocking issues found. This is a solid, well-tested fix for a real indirect-prompt-injection vector. A few minor observations:
The truncation ordering (neutralize first with |
What does this PR do?
build_local_comment_prompt/build_whole_comment_promptrender the Feishu comment-card timeline as[user_id] textlines joined with newlines into the prompt that_run_comment_agenthands to the model. The commenttextis untrusted — any collaborator on the document can write a comment, and_extract_reply_textpreserves its embedded newlines — yet it was interpolated raw.A comment such as:
therefore breaks out of its timeline line and poses as a fresh markdown section (a fake
## SYSTEM/## Overrideheading) inside the prompt the model reads when it responds to the comment mention.This is the same indirect-prompt-injection vector already closed for the sibling untrusted sinks: the sender-name prefix (
neutralize_untrusted_inline_text), the reply quote, and the Slack/Discord thread-context backfills. The Feishu comment-card timeline was the missed sink.The fix routes both builders through a small
_timeline_entry_linehelper that flattens each entry withneutralize_untrusted_inline_text.max_chars=0defers length capping to the existing_truncate, so the per-comment prompt limit is unchanged and a well-behaved comment renders as before.Related Issue
Fixes #
Type of Change
Changes Made
plugins/platforms/feishu/feishu_comment.py— add_timeline_entry_line, which renders one[user_id] texttimeline entry withtextpassed throughneutralize_untrusted_inline_text(max_chars=0, so the existing_truncatestill owns length). Bothbuild_local_comment_promptandbuild_whole_comment_promptnow build their timeline lines through it instead of interpolating raw.tests/gateway/test_feishu_comment.py— addTestCommentTimelineInjection: a hostile comment carrying an embedded## SYSTEMheading through both builders, asserting no injected line/heading survives, the content stays present (flattened), and a benign entry is unaffected.How to Test
Reproduce on the current code (pure function, no mocks):
Apply the fix; the entry collapses to one inert line, so
"\n## SYSTEM:" not in p.Run:
The new tests pass with the fix and fail without it; full file: 22 passed.
Checklist
Code
fix(feishu):)pytest tests/gateway/test_feishu_comment.py -qand all tests passDocumentation & Housekeeping
cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A