fix(channels): deliver send_file via Telegram and harden cross-channel dispatch#56
Merged
pufit merged 1 commit intoApr 30, 2026
Conversation
…l dispatch Phantom `[mcp__nerve__send_file]` headers in Telegram fixed: the tool now actually delivers files via the bound channel, not just persists a DB record for the web frontend. - `SEND_FILES` channel capability + `BaseChannel.send_file` interface. - Telegram `send_document` impl (path/exists checks; relies on Telegram API to enforce per-deployment size limits — works on self-hosted Bot API where the cap is 2 GiB, not just 50 MiB on api.telegram.org). - Web `send_file` no-ops since the existing `SendFileBlock` card already handles UI delivery. Cross-channel hardening: - `AgentEngine` tracks active channel per session (`_active_channel` set in `run()`, cleared on exit) and exposes `get_active_channel(session_id)`. - `ChannelRouter.send_file(session_id, file_path, channel=...)` requires an explicit channel. Cached `_message_context` target is reused only when its channel matches the requested one — never to pick the destination channel. `channel=None` returns False unconditionally (cron / planner / notifications / web routes that don't pass a channel through `engine.run()` no longer leak files to a stale Telegram chat from a prior inbound message). - `_send_file_impl` reads `engine.get_active_channel` and forwards. - Path-aware workspace containment check (`Path.relative_to` / `ValueError`) replaces the bypassable `str.startswith` guard — closes a sibling-prefix exfiltration vector exposed by real Telegram delivery. Test coverage: 28 unit tests across router, Telegram channel, tool impl, engine accessor — including the cross-channel leakage scenarios and sibling-prefix bypass attempt. 394 passed, 2 skipped on full suite.
pufit
approved these changes
Apr 30, 2026
Member
pufit
left a comment
There was a problem hiding this comment.
Thank you for your contribution! This is a great feature!
neomnezia
added a commit
to neomnezia/nerve
that referenced
this pull request
Apr 30, 2026
Resolves conflict in nerve/channels/telegram.py by taking upstream version (fork's pre-squash drafts of PRs ClickHouse#55 and ClickHouse#56 superseded by upstream squash-merges). Brings in: - PR ClickHouse#60 — optional Langfuse observability - PR ClickHouse#56 — send_file Telegram delivery + cross-channel hardening - PR ClickHouse#55 — adaptive-thinking effort cap per model Drops fork-unique tests/test_telegram_send.py: imported symbols (FLOODWAIT_GAP_S, PREVIEW_FOOTER) that upstream squash-refactored away; upstream's tests/test_send_file.py provides equivalent coverage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mcp__nerve__send_filewas leaving phantom[mcp__nerve__send_file]headers in Telegram and only persisting a DB record for the web frontend — files never reached the chat. This PR makes the tool actually deliver via the bound channel and closes three P1 cross-channel leakage paths.What changed
SEND_FILESchannel capability +BaseChannel.send_fileinterface.send_documentimpl with path/exists checks. No client-side size cap — the Telegram API enforces per-deployment limits (50 MiB onapi.telegram.org, 2 GiB on self-hosted Bot API), and the surrounding try/except already handles the rejection.send_fileno-ops sinceSendFileBlockalready handles UI delivery.Cross-channel hardening
AgentEnginetracks active channel per session (_active_channelset inrun(), cleared on exit) and exposesget_active_channel(session_id).ChannelRouter.send_file(session_id, file_path, channel=...)requires an explicit channel. Cached_message_contexttarget is reused only when its channel matches the requested one — never to pick the destination channel.channel=Nonereturns False unconditionally, so cron/planner/notifications/web routes that don't thread a channel throughengine.run()can no longer leak files to a stale Telegram chat from a prior inbound message._send_file_implreadsengine.get_active_channeland forwards.Path.relative_to/ValueError) replaces the bypassablestr.startswithguard — closes a sibling-prefix exfiltration vector exposed by real Telegram delivery (e.g. workspace/srv/wspreviously accepted/srv/ws-evil/secret.txt).Files
nerve/channels/base.py—Capability.SEND_FILES,BaseChannel.send_fileabstract.nerve/channels/telegram.py—send_fileviabot.send_document, capability advertised.nerve/channels/web.py— no-op (UI already covered).nerve/channels/router.py— explicit-channel dispatch, cached-context match guard.nerve/agent/engine.py—_active_channelmap,get_active_channel(), run()/exit lifecycle.nerve/agent/tools.py—_send_file_implresolves channel via engine, path containment check.tests/test_send_file.py— 28 new tests (router dispatch, Telegram channel, tool impl, engine accessor, leakage + sibling-prefix bypass scenarios).Test plan
pytest tests/test_send_file.py -v→ 28/28 passsend_fileon a workspace file delivers the document to the chat🤖 Generated with Claude Code