Harden approval nonce check and expand prompt-injection markers - #16
Merged
Conversation
- 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>
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
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).mcp/src/telegram_mcp/approval_server.py): switched from!=tosecrets.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.mcp/src/telegram_mcp/prompt_safety.py):message_content_is_untrusted_instructiononly 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, andtelegram_confirmed_sendreplays the server-stored preview instead of trusting agent-supplied text at commit time). Confirmed via grep that these heuristics are only exercised byrelease_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_requireddefault (README.md): documented thefalsedefault 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 readingconfig.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:
file_path_policy.py): confirmed both call sites never passallowlisted_dirs, so validation is blocklist-only in practice. The concrete remaining gap (arbitrary files 3+ levels under$HOMEare 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~/Documentsor~/Desktop, without a corresponding win against a named threat — so left as-is rather than force it silently.auth.py(get_health_report/get_doctor_report): these functions back the localtelegram-mcp health/doctorCLI commands only (print(json.dumps(...))to local stdout) — not thehealth_check/doctor_checkMCP tools, which go through separateTelegramWrappermethods inclient.py. Also, the two fields that could carry path info (download_dir,session_path) are already explicit, intentional fields onHealthInfo/DoctorInforegardless of any exception. No remaining leak worth patching.Verification
(381 vs. baseline 379 — two new regression tests: constant-time nonce comparison, Russian prompt-injection markers.)
./scripts/safe-gatealso 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'stgon$PATHresolves to the primary checkout, not this secondary worktree — reproduced identically with this branch's changes fullygit stashed, confirming it's a pre-existing worktree/PATH artifact, not a regression from this diff (diff touches onlymcp/andREADME.md, nocontrol-plane/files).GitHub context
Follow-up to the code-review findings surfaced in this session; item #1 (fail-open
TELEGRAM_MCP_TOOL_PROFILEresolution) was already fixed separately in00a57aa.