Files

3.0 KiB
Raw Permalink Blame History

Hermes Telegram Picker — Audit

Deep-dive findings from a focused code audit of the Telegram inline-keyboard picker. Component: hermes-agent/plugins/platforms/telegram/adapter.py (gateway run.py orchestrates). Audit date: 2026-08-29.

Scope

  • Model picker (send_model_picker → _handle_model_picker_callback; callbacks mp:/mpg:/mpv:/mm:/mc:/mb/mx/mg:)
  • Choice picker (send_choice_picker → _handle_choice_picker_callback; callback cp:)
  • Shared pagination core _format_choice_page in gateway/platforms/base.py (~line 4333)

Finding (broken / inconsistent)

  • [BUG — auth gap] _handle_model_picker_callback performs NO _is_callback_user_authorized gate. Every other state-flipping inline-button path in the same adapter gates:
    • choice picker (_handle_choice_picker_callback, line ~6673) — gated
    • approval buttons (ea:, lines ~7238 / ~7418) — gated
    • slash-confirm (sc:, ~7318) — gated
    • clarify (offer_job / scan, ~7531 / ~7602) — gated
    • Only the model picker is ungated.
  • Consequence: in a shared group/supergroup/forum, any user who can reach the picker message can tap buttons and switch the session's active model (global-ish config flip) without being an allowed user. The choice-picker comment explicitly states the intent: "unauthorized users in a shared group must not flip session/config state via someone else's picker message" — the model picker violates exactly that rule, for the highest-impact state.
  • Severity: medium (needs a group context + picker message reachable; DMs are single-user so only group/forum/channel contexts are exposed). A security inconsistency, not an outage.

Non-issues confirmed (looked, OK)

  • Callback prefix routing ("mp:","mpg:","mpv:","mm:","mc:","mb","mx","mg:") — ordering safe; mpg: does not collide with mp: (different 2nd char), mc: present, no prefix collision.
  • _format_choice_page pagination math (clamp, slice, (N–M of T) suffix) is correct.
  • _build_model_keyboard uses global abs_idx = start + i so mm: maps correctly across pages.
  • mx:noop page-counter button handled by the catch-all branch.
  • Choice-picker button indices cp:{i} use the flat local enumerate — correct for a single level.

Add the same _is_callback_user_authorized gate at the top of _handle_model_picker_callback (mirror the choice-picker gate at line ~6673). Gate before mutating _model_picker_state.

Owner / status

  • ✅ FIXED (2026-08-29) — committed 38622dc to local hermes-agent checkout. Fix is structural / regression-proof: moved the auth gate to the single dispatch point in _handle_callback_query (telegram adapter.py ~7199), covering ALL picker callback prefixes (mp:/mpg:/mpv:/mm:/mc:/mb/mx/mg:/cp:) in one place. Any future picker callback prefix added there is auto-protected. py_compile OK.
  • Activation pending: running gateway PID 22864 loaded pre-patch code; will pick up the fix on its next (supervisor) restart. Do NOT force-kill mid-conversation.