Skip to content

fix(integrations): escape telegram text so titles with < or & still deliver - #172

Open
abnegate wants to merge 1 commit into
migrate/httpfrom
migrate/clb
Open

abnegate wants to merge 1 commit into
migrate/httpfrom
migrate/clb

Conversation

@abnegate

@abnegate abnegate commented Oct 9, 2026

Copy link
Copy Markdown
Member

Stacked on #171; depends on #161 (Docker toolchain).

What changed

  • TelegramNotifier::send_message_with_ids escapes &, <, > and " in the message text before posting it with parse_mode: "HTML". Every notify_* and ask_question goes through this one path, so issue titles, sources, errors, PR URLs, trigger reasons, completion reasons and questions are all escaped.
  • The escaping lives in a new notifier/html.rs (html::escape), next to the notifiers that render HTML.
  • Truncation to Telegram's 4096-character limit now runs on the plain text, before escaping, with the limit and the ... marker as named constants.
  • The file's // --- ... --- section-header comments are removed.

Why

Telegram parses these messages as HTML, but every value was interpolated raw. A title like Vec<u8> & co makes Telegram reject the message as malformed HTML (so the notification is lost), and a crafted title or error could inject markup into the chat.

None of the message templates contain markup of their own, so the whole text is escaped once at the send boundary instead of per interpolated value. That way a future interpolation can't skip the escape. Escaping after truncation means the cut can never split an entity such as &amp;, and Telegram counts its limit on the parsed text.

How it was verified

  • claudear-verify.sh /Users/jakebarnby/Local/claudear/.claude/worktrees/migrate-clb clb ready: passed (fmt, clippy -D warnings, workspace tests).
  • New regression tests check the request body the notifier sends, through the existing MockTelegramClient:
    • test_notify_start_escapes_html_in_title: a title of <script>alert(1)</script> & "co" is posted as &lt;script&gt;alert(1)&lt;/script&gt; &amp; &quot;co&quot;.
    • test_notify_failed_escapes_html_in_error: an error containing Vec<u8> and & is escaped.
    • test_send_message_truncates_before_escaping: 5000 & characters come out as exactly 4082 whole &amp; entities followed by ..., with no split entity.
  • I watched all three fail before the fix: with the tests added and the fix not yet applied, claudear-verify.sh … clb scoped - -- -p claudear-integrations --lib notifier::telegram gave 121 passed and 3 failed, and the failures showed the raw <script>, & and Vec<u8> text in the posted body.
  • html.rs has unit tests for markup characters, re-escaping existing entities, and unchanged plain text.

What was NOT verified

  • No message was sent to the real Telegram Bot API; the tests stop at the request body.

Behaviour differences

  • Text that contains &, <, > or " now shows in Telegram exactly as written. Before, it either broke delivery or was rendered as markup.
  • Reply matching for ask_question is unchanged: Telegram returns the parsed (unescaped) text, which still contains Human input needed for <short id>.
  • A message over the limit is still cut to 4093 bytes of plain text plus .... The escaped payload can now be longer than 4096 bytes, which Telegram accepts because it counts characters after parsing entities.

🤖 Generated with Claude Code

…eliver

Telegram parses every message with parse_mode HTML, but issue titles,
errors, URLs and trigger reasons went in raw. A title such as
"Vec<u8> & co" made Telegram reject the message as malformed HTML, and
a crafted title could inject markup into the chat.

None of the message templates carry markup of their own, so the text is
escaped once at the send boundary instead of per interpolated value:
no future interpolation can forget it. Truncation to Telegram's 4096
character limit now happens on the plain text before escaping, since
the limit counts parsed characters and cutting after escaping could
split an entity.

Also removes the file's section-header comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hansi-codes

hansi-codes Bot commented Oct 9, 2026

Copy link
Copy Markdown

🟢 Tier S · Ready to merge

The change preserves plain-text message contents while safely escaping HTML metacharacters, and no actionable defects were found.

Adds shared HTML escaping for Telegram message text and applies it at the send boundary after plain-text truncation. Adds unit and request-body regression tests for markup characters, errors, and truncation.

Verdict New comments Fixed Still open
✅ Approved 0 0 0
📂 Walkthrough · 3
File Change
crates/claudear-integrations/src/notifier/html.rs Adds an HTML text escaper and focused unit tests.
crates/claudear-integrations/src/notifier/mod.rs Registers the notifier HTML helper module.
crates/claudear-integrations/src/notifier/telegram.rs Escapes outbound message text after truncation and adds Telegram request-body regression tests.

Reviewed 46b55cf · Details · Comment @hansi-codes review to re-run, or mention @hansi-codes with a question.

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Tier S · Looks good to merge. Summary

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant