Description
When dmPolicy is set to "allowlist", the bot still responds to non-allowlisted users who send /start, /help, or /status commands. Per ACCESS.md, allowlist policy should "Drop silently. No reply."
Root cause
The gate() function (line 218) correctly drops non-allowlisted DMs in allowlist mode. However, the /start, /help, and /status bot command handlers are registered separately and do not call gate(). They have their own incomplete access checks:
/start — only checks for disabled policy, not allowlist
/help — no policy check at all
/status — no policy check; also tells non-allowlisted users "Not paired. Send me a message to get a pairing code" even when pairing is disabled via allowlist policy
Impact
- Information leak: confirms bot is running to anyone who discovers its username
- Spam vector: the exact scenario
allowlist policy is designed to prevent (per ACCESS.md: "Useful if the bot's username is guessable and pairing replies would attract spam")
- Minor DoS surface: bot processes and replies to every command instead of silently dropping
Steps to reproduce
- Start Claude Code with the Telegram plugin
- Set policy:
/telegram:access policy allowlist
- Have a non-allowlisted user send
/start to the bot
- Expected: silence
- Actual: bot replies with pairing instructions
Proposed fix
Add dmPolicy and allowFrom checks to all three command handlers, consistent with gate()'s logic:
- Guard
!ctx.from unconditionally (matching gate() behavior)
- Return silently when
dmPolicy === "disabled"
- Return silently when
dmPolicy === "allowlist" and sender not in allowFrom
- Call
pruneExpired() in /status before iterating access.pending (prevents showing expired pairing codes)
Description
When
dmPolicyis set to"allowlist", the bot still responds to non-allowlisted users who send/start,/help, or/statuscommands. Per ACCESS.md,allowlistpolicy should "Drop silently. No reply."Root cause
The
gate()function (line 218) correctly drops non-allowlisted DMs inallowlistmode. However, the/start,/help, and/statusbot command handlers are registered separately and do not callgate(). They have their own incomplete access checks:/start— only checks fordisabledpolicy, notallowlist/help— no policy check at all/status— no policy check; also tells non-allowlisted users "Not paired. Send me a message to get a pairing code" even when pairing is disabled viaallowlistpolicyImpact
allowlistpolicy is designed to prevent (per ACCESS.md: "Useful if the bot's username is guessable and pairing replies would attract spam")Steps to reproduce
/telegram:access policy allowlist/startto the botProposed fix
Add
dmPolicyandallowFromchecks to all three command handlers, consistent withgate()'s logic:!ctx.fromunconditionally (matchinggate()behavior)dmPolicy === "disabled"dmPolicy === "allowlist"and sender not inallowFrompruneExpired()in/statusbefore iteratingaccess.pending(prevents showing expired pairing codes)