Skip to content

fix(gateway): sanitize lone surrogates before platform delivery - #55326

Open
AlexFucuson9 wants to merge 1 commit into
NousResearch:mainfrom
AlexFucuson9:fix/gateway-sanitize-surrogates-before-delivery
Open

fix(gateway): sanitize lone surrogates before platform delivery#55326
AlexFucuson9 wants to merge 1 commit into
NousResearch:mainfrom
AlexFucuson9:fix/gateway-sanitize-surrogates-before-delivery

Conversation

@AlexFucuson9

Copy link
Copy Markdown
Contributor

Fixes #55309

_sanitize_gateway_final_response() redacted secrets and classified provider errors but did not strip lone surrogate code points (U+D800-U+DFFF). These are invalid in UTF-8 and crash the utf-16-le length check in Telegram delivery, aborting the send for the entire reply.

The codebase already had _sanitize_surrogates() in agent/message_sanitization.py (used for history storage), but it was never applied to the delivered copy.

Fix: Apply _sanitize_surrogates() as the final step in _sanitize_gateway_final_response() so all chat surfaces receive surrogate-safe text.

Reproduction (before fix):

content = "Here is your answer \ud800 done"
utf16_len(content)  # UnicodeEncodeError

After fix: lone surrogates are replaced with U+FFFD before reaching platform adapters.

Fixes NousResearch#55309

_sanitize_gateway_final_response() redacted secrets and classified
provider errors but did not strip lone surrogate code points
(U+D800-U+DFFF).  These are invalid in UTF-8 and crash the utf-16-le
length check in Telegram delivery, aborting the send for the entire
reply.

The codebase already had _sanitize_surrogates() in
agent/message_sanitization.py (used for history storage), but it was
never applied to the delivered copy.  Add it as the final step in
_sanitize_gateway_final_response() so all chat surfaces receive
surrogate-safe text.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages P2 Medium — degraded but workaround exists duplicate This issue or pull request already exists labels Jun 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #55310 (earliest open canonical fix for #55309, paired with the issue by its author). Same _sanitize_surrogates() application in _sanitize_gateway_final_response() (gateway/run.py). Related: #55309 (issue), #55143/#55144 (Signal sibling of the same lone-surrogate class).

@tonydwb tonydwb 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.

LGTM. Strips lone surrogate code points that crash utf-16 encoding in platform length checks. Clean fix.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 15, 2026

@teknium1 teknium1 left a comment

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.

Thanks for tracing the non-streaming final-response boundary; current main still returns ordinary chat replies unsanitized at gateway/run.py:434-437, so that portion addresses a real defect.

Problems

  • Streaming bypasses this helper: gateway/run.py:18123-18125 forwards raw deltas to GatewayStreamConsumer.on_delta, which queues them unchanged at gateway/stream_consumer.py:370-379 and later edits through the adapter at gateway/stream_consumer.py:1771-1775. Telegram measures edit content with strict UTF-16 encoding at plugins/platforms/telegram/adapter.py:3980, so a lone surrogate can still abort delivery before this final-response path runs.
  • The diff changes only gateway/run.py; it adds no regression test. tests/gateway/test_telegram_noise_filter.py:79-243 has final-response coverage but no lone-surrogate case.

Suggested changes

  • Retain the final-response sanitization and sanitize deltas at the stream-consumer input boundary; add non-streaming and streaming Telegram regressions that prove the downstream UTF-16 measurement accepts the delivered content.
  • Keep raw programmatic surfaces unchanged, consistent with gateway/run.py:421-427.

Automated hermes-sweeper review.

Comment thread gateway/run.py
# utf-16-le encoding in platform length checks (e.g. Telegram).
from agent.message_sanitization import _sanitize_surrogates
return _sanitize_surrogates(redacted)

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.

Please add a regression test for this final-response boundary, and cover the streaming path separately: raw deltas bypass this helper via gateway/run.py:18123-18125GatewayStreamConsumer.on_delta, then reach adapter edits before the final sanitizer can run.

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

Labels

comp/gateway Gateway runner, session dispatch, delivery duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lone surrogate in a reply crashes Telegram delivery: the delivered response is not surrogate-sanitized

4 participants