Skip to content

Harden approval nonce check and expand prompt-injection markers - #16

Merged
speech115 merged 1 commit into
mainfrom
claude/harden-telegram-mcp-suggestions
Jul 1, 2026
Merged

Harden approval nonce check and expand prompt-injection markers#16
speech115 merged 1 commit into
mainfrom
claude/harden-telegram-mcp-suggestions

Conversation

@speech115

Copy link
Copy Markdown
Owner

Summary

Follow-up from a full-project code review (/code-review) covering three of the five remaining Suggestions from that review; the two others were re-assessed as not warranting a code change (see below).

  • Approval nonce comparison (mcp/src/telegram_mcp/approval_server.py): switched from != to secrets.compare_digest, matching the constant-time comparison already used for the MCP bearer token (StaticBearerTokenVerifier). Loopback-only exposure kept this low severity, but there's no reason for the two comparisons in this codebase to use different rigor.
  • Prompt-injection markers (mcp/src/telegram_mcp/prompt_safety.py): message_content_is_untrusted_instruction only matched English substrings ("ignore previous instructions", "system prompt", "you are now"). Added Russian equivalents, and added a module docstring making explicit that this is a best-effort heuristic, not the actual defense — the real defense is architectural (retrieved content is always quoted as untrusted, sends require an explicit stable dialog target, and telegram_confirmed_send replays the server-stored preview instead of trusting agent-supplied text at commit time). Confirmed via grep that these heuristics are only exercised by release_gates.py's self-check, not wired into any live tool call path — so they were never the primary guard to begin with.
  • write_approval_required default (README.md): documented the false default and what it does/doesn't buy you (anti-tamper preview replay + write-audit log, but no human-click gate) — this was previously only discoverable by reading config.py. Did not flip the default, since that's a behavior change that would newly require a localhost web click before every send for existing deployments; flagging this here for a separate decision if you want it.

Re-assessed, no code change:

  • Outbound file path policy allowlist gap (file_path_policy.py): confirmed both call sites never pass allowlisted_dirs, so validation is blocklist-only in practice. The concrete remaining gap (arbitrary files 3+ levels under $HOME are sendable) is inherent to a file-send tool's purpose — the actually-sensitive categories (.env, .session, tdata, cloud-sync folders, archives, symlinks, dot-prefixed dirs like .ssh) are already blocked regardless of depth. Flipping to allowlist-only-by-default would be a breaking behavior change for anyone currently sending files from ~/Documents or ~/Desktop, without a corresponding win against a named threat — so left as-is rather than force it silently.
  • Exception message leakage in auth.py (get_health_report/get_doctor_report): these functions back the local telegram-mcp health/doctor CLI commands only (print(json.dumps(...)) to local stdout) — not the health_check/doctor_check MCP tools, which go through separate TelegramWrapper methods in client.py. Also, the two fields that could carry path info (download_dir, session_path) are already explicit, intentional fields on HealthInfo/DoctorInfo regardless of any exception. No remaining leak worth patching.

Verification

$ cd mcp && PYTHONPATH=src .venv/bin/python -m unittest discover -s tests -p 'test_*.py'
Ran 381 tests in 11.461s
OK

(381 vs. baseline 379 — two new regression tests: constant-time nonce comparison, Russian prompt-injection markers.)

$ git diff --check
(clean)

$ mcp/bin/sync-agent-docs --plugin-dir plugin --check --no-restart --json
{"status": "ok", ..., "drift": []}

./scripts/safe-gate also runs the control-plane suite; two unrelated tests there (test_fast_read_adapter_is_registered_as_safe_first_path, test_registry_includes_fast_read_adapter_component) fail in this worktree because the machine's tg on $PATH resolves to the primary checkout, not this secondary worktree — reproduced identically with this branch's changes fully git stashed, confirming it's a pre-existing worktree/PATH artifact, not a regression from this diff (diff touches only mcp/ and README.md, no control-plane/ files).

GitHub context

Follow-up to the code-review findings surfaced in this session; item #1 (fail-open TELEGRAM_MCP_TOOL_PROFILE resolution) was already fixed separately in 00a57aa.

- Compare approval nonces with secrets.compare_digest instead of != to
  match the constant-time comparison already used for the MCP bearer
  token.
- Add Russian-language untrusted-instruction markers to
  message_content_is_untrusted_instruction and document in the module
  docstring that these heuristics are best-effort, not a complete
  defense (the real defense is architectural: quoted untrusted content,
  explicit dialog targets, and server-replayed confirmed sends).
- Document the write_approval_required=false default in README so the
  approval-gate trade-off (anti-tamper + audit log, no human click) is
  explicit rather than discoverable only by reading config.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@speech115
speech115 merged commit 27060bb into main Jul 1, 2026
2 checks passed
@speech115
speech115 deleted the claude/harden-telegram-mcp-suggestions branch July 1, 2026 09:01
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