claude-telegram-interface connects a Telegram chat to a Claude Code CLI running on
your machine with access to a directory you choose. Treat it as what it is: a
remote doorway to a capable agent. Read this before exposing it.
The bot has no inbound network surface (it long-polls Telegram; it opens no ports). The only way to reach the agent is to send the bot a Telegram message and have your numeric user id on the allowlist. The allowlist is the only door.
allowed_user_idsinconfig.tomlis the sole authentication boundary. A message from any id not on that list is silently dropped — the sender gets no reply and no signal the bot exists.- There are no roles, no per-user scoping, no identity attestation beyond the Telegram user id. Keep the list to ids you control.
- Anyone on the allowlist can make the agent do anything the agent can do in
working_dir. Only list yourself (and people you fully trust at that level). - Your Telegram account IS the key. The gate authenticates the Telegram user id — it cannot tell you from someone logged in as you. A compromised Telegram account on the allowlist is a compromised agent. Protect the account itself (enable Telegram's two-step verification).
- Keep it 1:1. Don't add the bot to group chats, and treat its chat like a shell prompt on your machine — because that is what it is.
By default the spawn runs in Claude Code's normal permission mode. If you set
bypass_permissions = true, the headless spawn runs with
--permission-mode bypassPermissions, so it can execute tools (bash, git, file
writes) without interactive approval. That is powerful and dangerous.
For that reason bypass_permissions is interlocked: the bot refuses to start
with bypass on unless a write-jail is in place (see config.py) — on macOS a
sandbox_profile, on Linux linux_confined = true (and never as root).
- macOS: point
sandbox_profileat a Seatbelt profile (seeexamples/example.sb) that confines writes to yourworking_dir(plus~/.claude, which--resumeneeds, and temp dirs). - Linux: there is no Seatbelt; the confinement layer is systemd. The unit in
deploy/runs the bot as an unprivileged user withProtectSystem=strict+ scopedReadWritePaths— the kernel confines writes to the working dir and agent state dirs, reproducing the macOS boundary.linux_confined = trueis your attestation that this unit is actually in place (the engine cannot verify systemd from inside; it does verify it is not running as root). Don't set it outside that unit. - The jail is write-only by design: reads, exec, network, and credentials (Keychain on macOS) stay open, so credential-based helper CLIs keep authenticating. It guards against an unintended write outside your project — not against network egress.
-
The bot token lives only in
config.toml, which is gitignored. Never commit it. Useconfig.example.toml(placeholder token) for samples. -
A portable pre-commit secret gate is included at
scripts/pre-commit-secret-gate.sh. It blocks committing a realconfig.tomlor any Telegram-token-shaped string. Install it in your clone:cp scripts/pre-commit-secret-gate.sh .git/hooks/pre-commit chmod +x .git/hooks/pre-commit
The hook is client-side and
--no-verify-bypassable — it stops accidents, not a determined push. For a server-side guarantee, enable GitHub push protection (Secret scanning) on the repo. -
If a token ever leaks, rotate it immediately via @BotFather (
/revokeor/token).
This is a small personal project. Open an issue for security concerns; do not include real tokens, ids, or paths in the report.