claude-telegram-mirror 0.1.27 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -91
- package/SECURITY.md +228 -0
- package/package.json +14 -37
- package/postinstall.cjs +44 -40
- package/scripts/ctm-wrapper.cjs +80 -0
- package/scripts/resolve-binary.cjs +213 -0
- package/dist/bot/commands.d.ts +0 -41
- package/dist/bot/commands.d.ts.map +0 -1
- package/dist/bot/commands.js +0 -240
- package/dist/bot/commands.js.map +0 -1
- package/dist/bot/formatting.d.ts +0 -62
- package/dist/bot/formatting.d.ts.map +0 -1
- package/dist/bot/formatting.js +0 -295
- package/dist/bot/formatting.js.map +0 -1
- package/dist/bot/telegram.d.ts +0 -105
- package/dist/bot/telegram.d.ts.map +0 -1
- package/dist/bot/telegram.js +0 -458
- package/dist/bot/telegram.js.map +0 -1
- package/dist/bot/types.d.ts +0 -28
- package/dist/bot/types.d.ts.map +0 -1
- package/dist/bot/types.js +0 -5
- package/dist/bot/types.js.map +0 -1
- package/dist/bridge/daemon.d.ts +0 -159
- package/dist/bridge/daemon.d.ts.map +0 -1
- package/dist/bridge/daemon.js +0 -1083
- package/dist/bridge/daemon.js.map +0 -1
- package/dist/bridge/index.d.ts +0 -10
- package/dist/bridge/index.d.ts.map +0 -1
- package/dist/bridge/index.js +0 -9
- package/dist/bridge/index.js.map +0 -1
- package/dist/bridge/injector.d.ts +0 -112
- package/dist/bridge/injector.d.ts.map +0 -1
- package/dist/bridge/injector.js +0 -354
- package/dist/bridge/injector.js.map +0 -1
- package/dist/bridge/session.d.ts +0 -134
- package/dist/bridge/session.d.ts.map +0 -1
- package/dist/bridge/session.js +0 -440
- package/dist/bridge/session.js.map +0 -1
- package/dist/bridge/socket.d.ts +0 -97
- package/dist/bridge/socket.d.ts.map +0 -1
- package/dist/bridge/socket.js +0 -436
- package/dist/bridge/socket.js.map +0 -1
- package/dist/bridge/types.d.ts +0 -38
- package/dist/bridge/types.d.ts.map +0 -1
- package/dist/bridge/types.js +0 -5
- package/dist/bridge/types.js.map +0 -1
- package/dist/cli.d.ts +0 -7
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -576
- package/dist/cli.js.map +0 -1
- package/dist/hooks/handler.d.ts +0 -106
- package/dist/hooks/handler.d.ts.map +0 -1
- package/dist/hooks/handler.js +0 -463
- package/dist/hooks/handler.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -8
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -7
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/installer.d.ts +0 -56
- package/dist/hooks/installer.d.ts.map +0 -1
- package/dist/hooks/installer.js +0 -443
- package/dist/hooks/installer.js.map +0 -1
- package/dist/hooks/types.d.ts +0 -94
- package/dist/hooks/types.d.ts.map +0 -1
- package/dist/hooks/types.js +0 -6
- package/dist/hooks/types.js.map +0 -1
- package/dist/index.d.ts +0 -19
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -20
- package/dist/index.js.map +0 -1
- package/dist/service/doctor.d.ts +0 -10
- package/dist/service/doctor.d.ts.map +0 -1
- package/dist/service/doctor.js +0 -424
- package/dist/service/doctor.js.map +0 -1
- package/dist/service/manager.d.ts +0 -52
- package/dist/service/manager.d.ts.map +0 -1
- package/dist/service/manager.js +0 -596
- package/dist/service/manager.js.map +0 -1
- package/dist/service/setup.d.ts +0 -11
- package/dist/service/setup.d.ts.map +0 -1
- package/dist/service/setup.js +0 -628
- package/dist/service/setup.js.map +0 -1
- package/dist/utils/chunker.d.ts +0 -24
- package/dist/utils/chunker.d.ts.map +0 -1
- package/dist/utils/chunker.js +0 -123
- package/dist/utils/chunker.js.map +0 -1
- package/dist/utils/config.d.ts +0 -51
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js +0 -160
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -7
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -28
- package/dist/utils/logger.js.map +0 -1
- package/scripts/telegram-hook.sh +0 -423
package/README.md
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/claude-telegram-mirror)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
+

|
|
5
6
|
|
|
6
7
|
Bidirectional communication between Claude Code CLI and Telegram. Control your Claude Code sessions from your phone.
|
|
7
8
|
|
|
8
|
-
**Supported platforms:** Linux, macOS
|
|
9
|
+
**Supported platforms:** Linux x64, Linux arm64, macOS ARM64, macOS Intel x64
|
|
9
10
|
|
|
10
11
|
## Installation
|
|
11
12
|
|
|
@@ -14,21 +15,26 @@ npm install -g claude-telegram-mirror
|
|
|
14
15
|
ctm setup # Interactive setup wizard
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
1. Creating a Telegram bot via @BotFather
|
|
19
|
-
2. Disabling privacy mode (critical for group messages)
|
|
20
|
-
3. Setting up a supergroup with Topics
|
|
21
|
-
4. Verifying bot permissions
|
|
22
|
-
5. Installing hooks and the system service
|
|
18
|
+
This installs a native Rust binary (`ctm`) via platform-specific optional packages. No Node.js runtime is needed to run the binary itself — Node.js 18+ is only required as the npm distribution mechanism.
|
|
23
19
|
|
|
24
20
|
## Features
|
|
25
21
|
|
|
26
22
|
- **CLI to Telegram**: Mirror Claude's responses, tool usage, and notifications
|
|
27
23
|
- **Telegram to CLI**: Send prompts from Telegram directly to Claude Code
|
|
28
|
-
- **
|
|
24
|
+
- **Tool Summarizer**: Human-readable summaries for 30+ command patterns ("Running tests" instead of "Running: Bash")
|
|
25
|
+
- **AskUserQuestion Rendering**: Inline buttons for Claude's interactive questions
|
|
26
|
+
- **Photo & Document Upload**: Send images/files from Telegram, path injected into Claude
|
|
27
|
+
- **Stop/Interrupt**: Type `stop` to send Escape, `kill` to send Ctrl-C
|
|
29
28
|
- **Session Threading**: Each Claude session gets its own Forum Topic
|
|
29
|
+
- **Session Rename**: `/rename` syncs with Claude Code's session title
|
|
30
30
|
- **Multi-System Support**: Run separate daemons on multiple machines
|
|
31
31
|
- **Compaction Notifications**: Get notified when Claude summarizes context
|
|
32
|
+
- **Governor Rate Limiting**: MessageQueue with retry and exponential backoff
|
|
33
|
+
- **Doctor Auto-Fix**: `ctm doctor --fix` auto-remediates common issues
|
|
34
|
+
- **Token Scrubbing**: Global regex-based scrubbing prevents bot tokens from leaking to logs
|
|
35
|
+
- **Atomic PID Locking**: `flock(2)` prevents duplicate daemon instances
|
|
36
|
+
- **Path Traversal Protection**: Transcript paths validated and canonicalized before file access
|
|
37
|
+
- **Char-Boundary Safe**: Unicode-safe message chunking and string truncation throughout
|
|
32
38
|
|
|
33
39
|
## Quick Start
|
|
34
40
|
|
|
@@ -53,6 +59,7 @@ claude
|
|
|
53
59
|
# Setup & diagnostics
|
|
54
60
|
ctm setup # Interactive setup wizard
|
|
55
61
|
ctm doctor # Diagnose configuration issues
|
|
62
|
+
ctm doctor --fix # Auto-fix detected issues
|
|
56
63
|
|
|
57
64
|
# Daemon control
|
|
58
65
|
ctm start # Start daemon (foreground mode)
|
|
@@ -61,6 +68,9 @@ ctm stop --force # Force stop if graceful shutdown fails
|
|
|
61
68
|
ctm restart # Restart daemon
|
|
62
69
|
ctm status # Show daemon status, config, and hooks
|
|
63
70
|
ctm config --test # Test Telegram connection
|
|
71
|
+
ctm toggle # Toggle mirroring on/off
|
|
72
|
+
ctm toggle --on # Force mirroring ON
|
|
73
|
+
ctm toggle --off # Force mirroring OFF
|
|
64
74
|
|
|
65
75
|
# Hook management
|
|
66
76
|
ctm install-hooks # Install global hooks
|
|
@@ -77,19 +87,28 @@ ctm service restart # Restart via service manager
|
|
|
77
87
|
ctm service status # Show service status
|
|
78
88
|
```
|
|
79
89
|
|
|
80
|
-
**Note:**
|
|
90
|
+
**Note:** `ctm stop` and `ctm restart` auto-detect whether the daemon is running directly or via a system service and use the appropriate method.
|
|
81
91
|
|
|
82
92
|
## Telegram Commands
|
|
83
93
|
|
|
84
|
-
Once connected, you can control Claude from Telegram:
|
|
85
|
-
|
|
86
94
|
| Command | Action |
|
|
87
95
|
|---------|--------|
|
|
88
96
|
| Any text | Sends to Claude as input |
|
|
89
97
|
| `stop` | Sends Escape to pause Claude |
|
|
90
98
|
| `kill` | Sends Ctrl-C to exit Claude entirely |
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
| `cc <cmd>` | Sends `/<cmd>` as a slash command to Claude |
|
|
100
|
+
| `/status` | Show active sessions and mirroring state |
|
|
101
|
+
| `/sessions` | List active sessions with age and project dir |
|
|
102
|
+
| `/rename <name>` | Rename session (syncs with Claude Code) |
|
|
103
|
+
| `/attach <id>` | Attach to a session for updates |
|
|
104
|
+
| `/detach` | Detach from current session |
|
|
105
|
+
| `/mute` / `/unmute` | Suppress/resume agent response notifications |
|
|
106
|
+
| `/toggle` | Toggle mirroring on/off |
|
|
107
|
+
| `/abort` | Abort the attached session |
|
|
108
|
+
| `/ping` | Measure round-trip latency |
|
|
109
|
+
| `/help` | Show all commands |
|
|
110
|
+
|
|
111
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for additional details and command aliases.
|
|
93
112
|
|
|
94
113
|
### Tool Approval Buttons
|
|
95
114
|
|
|
@@ -102,36 +121,32 @@ When Claude requests to use a tool that requires permission (Write, Edit, Bash w
|
|
|
102
121
|
| **Abort** | Stop the entire Claude session |
|
|
103
122
|
| **Details** | View full tool input parameters |
|
|
104
123
|
|
|
105
|
-
|
|
124
|
+
Approval buttons only appear in normal mode, not with `--dangerously-skip-permissions`. If you don't respond within 5 minutes, Claude falls back to CLI approval.
|
|
106
125
|
|
|
107
126
|
## Architecture
|
|
108
127
|
|
|
109
128
|
```
|
|
110
129
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
111
|
-
│ Claude Code │────▶│
|
|
112
|
-
│ CLI │◀────│
|
|
130
|
+
│ Claude Code │────▶│ ctm daemon │────▶│ Telegram │
|
|
131
|
+
│ CLI │◀────│ (Rust binary) │◀────│ Bot │
|
|
113
132
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
114
133
|
│ │
|
|
115
134
|
│ hooks │ Unix socket
|
|
116
135
|
▼ ▼
|
|
117
136
|
┌─────────────────┐ ┌─────────────────┐
|
|
118
|
-
│
|
|
119
|
-
│
|
|
120
|
-
│
|
|
121
|
-
├─────────────────┤ │ │
|
|
122
|
-
│ Other hooks: │────▶│ │
|
|
123
|
-
│ Bash script │ │ │
|
|
124
|
-
│ (fire & forget)│ │ │
|
|
137
|
+
│ ctm hook │────▶│ Socket Server │
|
|
138
|
+
│ (same binary, │◀────│ (bidirectional)│
|
|
139
|
+
│ hook mode) │ │ │
|
|
125
140
|
└─────────────────┘ └─────────────────┘
|
|
126
141
|
```
|
|
127
142
|
|
|
128
143
|
**Flow:**
|
|
129
|
-
1. Claude Code hooks
|
|
130
|
-
2. PreToolUse:
|
|
131
|
-
3. Other hooks:
|
|
132
|
-
4.
|
|
144
|
+
1. Claude Code hooks invoke `ctm hook`, which reads the event from stdin
|
|
145
|
+
2. PreToolUse: sends approval request via socket, waits for Telegram response
|
|
146
|
+
3. Other hooks: sends JSON to daemon via socket and exits immediately
|
|
147
|
+
4. Daemon forwards messages to Telegram Forum Topic with summarized tool actions
|
|
133
148
|
5. Telegram replies are injected into CLI via `tmux send-keys`
|
|
134
|
-
6. Stop commands send
|
|
149
|
+
6. Stop/kill commands send Escape or Ctrl-C to interrupt Claude
|
|
135
150
|
|
|
136
151
|
## Multi-System Architecture
|
|
137
152
|
|
|
@@ -146,11 +161,7 @@ When running Claude Code on multiple machines, each system needs its own bot to
|
|
|
146
161
|
### Setup for Multiple Systems
|
|
147
162
|
|
|
148
163
|
1. **Create one bot per system** via [@BotFather](https://t.me/botfather)
|
|
149
|
-
- System A: `@claude_mirror_system_a_bot`
|
|
150
|
-
- System B: `@claude_mirror_system_b_bot`
|
|
151
|
-
|
|
152
164
|
2. **Add all bots to the same supergroup** with admin permissions
|
|
153
|
-
|
|
154
165
|
3. **Configure each system** with its own bot token:
|
|
155
166
|
```bash
|
|
156
167
|
# On System A (~/.telegram-env)
|
|
@@ -161,23 +172,20 @@ When running Claude Code on multiple machines, each system needs its own bot to
|
|
|
161
172
|
export TELEGRAM_BOT_TOKEN="token-for-system-b-bot"
|
|
162
173
|
export TELEGRAM_CHAT_ID="-100shared-group-id" # Same group!
|
|
163
174
|
```
|
|
164
|
-
|
|
165
175
|
4. **Each daemon creates topics for its sessions** - Messages route correctly because each daemon only processes topics it created.
|
|
166
176
|
|
|
167
177
|
## Prerequisites
|
|
168
178
|
|
|
169
|
-
- Node.js 18+
|
|
179
|
+
- Node.js 18+ (for npm installation only)
|
|
170
180
|
- Claude Code CLI
|
|
171
181
|
- tmux (for bidirectional communication)
|
|
172
|
-
- jq (JSON processing)
|
|
173
|
-
- nc (netcat, for socket communication)
|
|
174
182
|
- Telegram account
|
|
175
183
|
|
|
176
184
|
## Telegram Setup
|
|
177
185
|
|
|
178
186
|
### 1. Create a Bot
|
|
179
187
|
|
|
180
|
-
1. Message [@BotFather](https://t.me/botfather)
|
|
188
|
+
1. Message [@BotFather](https://t.me/botfather) -> `/newbot`
|
|
181
189
|
2. Choose name and username (must end in `bot`)
|
|
182
190
|
3. Save the API token
|
|
183
191
|
|
|
@@ -185,27 +193,23 @@ When running Claude Code on multiple machines, each system needs its own bot to
|
|
|
185
193
|
|
|
186
194
|
1. Create a new group in Telegram
|
|
187
195
|
2. Add your bot to the group
|
|
188
|
-
3. Group Settings
|
|
196
|
+
3. Group Settings -> Enable **Topics**
|
|
189
197
|
|
|
190
198
|
### 3. Make Bot an Admin
|
|
191
199
|
|
|
192
|
-
1. Group Settings
|
|
200
|
+
1. Group Settings -> Administrators -> Add your bot
|
|
193
201
|
2. Enable: **Manage Topics**, **Post Messages**
|
|
194
202
|
|
|
195
203
|
### 4. Get Chat ID
|
|
196
204
|
|
|
197
205
|
1. Send any message in the group
|
|
198
|
-
2.
|
|
199
|
-
```bash
|
|
200
|
-
./scripts/get-chat-id.sh YOUR_BOT_TOKEN
|
|
201
|
-
```
|
|
202
|
-
Or manually: `https://api.telegram.org/botYOUR_TOKEN/getUpdates`
|
|
206
|
+
2. Visit `https://api.telegram.org/botYOUR_TOKEN/getUpdates`
|
|
203
207
|
3. Copy the chat ID (supergroups start with `-100`)
|
|
204
208
|
|
|
205
209
|
### 5. Disable Privacy Mode
|
|
206
210
|
|
|
207
|
-
1. [@BotFather](https://t.me/botfather)
|
|
208
|
-
2. Bot Settings
|
|
211
|
+
1. [@BotFather](https://t.me/botfather) -> `/mybots` -> Select bot
|
|
212
|
+
2. Bot Settings -> Group Privacy -> **Turn off**
|
|
209
213
|
|
|
210
214
|
## Configuration
|
|
211
215
|
|
|
@@ -248,7 +252,7 @@ Environment variables take precedence over config file values.
|
|
|
248
252
|
|
|
249
253
|
```bash
|
|
250
254
|
ctm doctor
|
|
251
|
-
# Checks:
|
|
255
|
+
# Checks: config, hooks, socket, tmux, systemd/launchd, Telegram API
|
|
252
256
|
```
|
|
253
257
|
|
|
254
258
|
## Project-Level Hooks
|
|
@@ -258,32 +262,35 @@ If your project has `.claude/settings.json` with custom hooks, global hooks are
|
|
|
258
262
|
```bash
|
|
259
263
|
cd /path/to/your/project
|
|
260
264
|
ctm install-hooks --project
|
|
261
|
-
# or shorthand:
|
|
262
|
-
ctm install-hooks -p
|
|
263
265
|
```
|
|
264
266
|
|
|
265
|
-
The installer will prompt you to set up project-level hooks during installation. You can also add them later to any project.
|
|
266
|
-
|
|
267
267
|
## How Messages Flow
|
|
268
268
|
|
|
269
269
|
| Direction | Event | Display |
|
|
270
270
|
|-----------|-------|---------|
|
|
271
|
-
| CLI
|
|
272
|
-
| CLI
|
|
273
|
-
| CLI
|
|
274
|
-
| CLI
|
|
275
|
-
| CLI
|
|
276
|
-
|
|
|
277
|
-
| Telegram
|
|
271
|
+
| CLI -> Telegram | User types | User (cli): ... |
|
|
272
|
+
| CLI -> Telegram | Tool starts | Running tests (summarized) |
|
|
273
|
+
| CLI -> Telegram | Claude responds | Claude: ... |
|
|
274
|
+
| CLI -> Telegram | Session starts | New Forum Topic created |
|
|
275
|
+
| CLI -> Telegram | Context compacting | Notification sent |
|
|
276
|
+
| CLI -> Telegram | AskUserQuestion | Inline buttons rendered |
|
|
277
|
+
| Telegram -> CLI | User sends message | Injected via tmux |
|
|
278
|
+
| Telegram -> CLI | User sends photo | Downloaded, path injected |
|
|
279
|
+
| Telegram -> CLI | User types "stop" | Sends Escape interrupt |
|
|
278
280
|
|
|
279
281
|
## Technical Details
|
|
280
282
|
|
|
283
|
+
- **Binary**: Single native Rust executable (`ctm`), ~10 MB
|
|
281
284
|
- **Session storage**: SQLite at `~/.config/claude-telegram-mirror/sessions.db`
|
|
282
285
|
- **Socket path**: `~/.config/claude-telegram-mirror/bridge.sock`
|
|
286
|
+
- **PID file**: `~/.config/claude-telegram-mirror/bridge.pid` (flock-guarded)
|
|
287
|
+
- **Downloads**: `~/.config/claude-telegram-mirror/downloads/` (0700 permissions)
|
|
283
288
|
- **Response extraction**: Reads Claude's transcript `.jsonl` on Stop event
|
|
284
289
|
- **Deduplication**: Telegram-originated messages tracked to prevent echo
|
|
285
290
|
- **Topic routing**: Each daemon only processes topics it created (multi-bot safe)
|
|
286
|
-
- **
|
|
291
|
+
- **Rate limiting**: Governor-based with exponential backoff retry queue
|
|
292
|
+
- **Token scrubbing**: All log output filtered through regex to strip bot tokens
|
|
293
|
+
- **Test suite**: 512 Rust tests (unit + 10 integration test files)
|
|
287
294
|
|
|
288
295
|
## Troubleshooting
|
|
289
296
|
|
|
@@ -291,10 +298,9 @@ Run the diagnostic tool first:
|
|
|
291
298
|
|
|
292
299
|
```bash
|
|
293
300
|
ctm doctor
|
|
301
|
+
ctm doctor --fix # Auto-fix common issues
|
|
294
302
|
```
|
|
295
303
|
|
|
296
|
-
This checks all common issues and provides fix suggestions.
|
|
297
|
-
|
|
298
304
|
### Common Issues
|
|
299
305
|
|
|
300
306
|
**Hooks not firing?**
|
|
@@ -305,12 +311,12 @@ This checks all common issues and provides fix suggestions.
|
|
|
305
311
|
**409 Conflict error?**
|
|
306
312
|
- Only one polling connection per bot token is allowed
|
|
307
313
|
- If running multiple systems, each needs its own bot (see Multi-System Architecture)
|
|
308
|
-
- Kill duplicate daemons: `
|
|
314
|
+
- Kill duplicate daemons: `ctm stop --force`
|
|
309
315
|
|
|
310
316
|
**Bridge not receiving events?**
|
|
311
317
|
- Check socket: `ls -la ~/.config/claude-telegram-mirror/bridge.sock`
|
|
312
|
-
-
|
|
313
|
-
-
|
|
318
|
+
- Check daemon logs for errors
|
|
319
|
+
- Run `ctm status` to verify daemon is running
|
|
314
320
|
|
|
315
321
|
**tmux injection not working?**
|
|
316
322
|
- Verify tmux session: `tmux list-sessions`
|
|
@@ -327,46 +333,55 @@ This checks all common issues and provides fix suggestions.
|
|
|
327
333
|
**Service not starting (macOS)?**
|
|
328
334
|
- Check status: `launchctl list | grep claude`
|
|
329
335
|
- View logs: `cat ~/Library/Logs/claude-telegram-mirror.*.log`
|
|
330
|
-
- Check permissions: Ensure Terminal has Accessibility access
|
|
331
336
|
|
|
332
|
-
##
|
|
337
|
+
## Build from Source
|
|
333
338
|
|
|
334
339
|
<details>
|
|
335
|
-
<summary>Click to expand
|
|
340
|
+
<summary>Click to expand</summary>
|
|
336
341
|
|
|
337
|
-
For developers who want to
|
|
342
|
+
For developers who want to build from source or contribute:
|
|
338
343
|
|
|
339
344
|
```bash
|
|
340
345
|
# 1. Clone and build
|
|
341
346
|
git clone https://github.com/robertelee78/claude-telegram-mirror.git
|
|
342
|
-
cd claude-telegram-mirror
|
|
343
|
-
|
|
344
|
-
#
|
|
347
|
+
cd claude-telegram-mirror/rust-crates
|
|
348
|
+
cargo build --release
|
|
349
|
+
# Binary at: rust-crates/target/release/ctm
|
|
345
350
|
|
|
346
|
-
#
|
|
351
|
+
# 2. Run tests
|
|
352
|
+
cargo test
|
|
347
353
|
|
|
348
|
-
#
|
|
349
|
-
./
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
cat > ~/.telegram-env << 'EOF'
|
|
353
|
-
export TELEGRAM_BOT_TOKEN="your-token-here"
|
|
354
|
-
export TELEGRAM_CHAT_ID="-100your-chat-id"
|
|
355
|
-
export TELEGRAM_MIRROR=true
|
|
356
|
-
EOF
|
|
354
|
+
# 3. Use the binary directly
|
|
355
|
+
./target/release/ctm setup
|
|
356
|
+
./target/release/ctm start
|
|
357
|
+
```
|
|
357
358
|
|
|
358
|
-
|
|
359
|
-
node dist/cli.js install-hooks # Global install
|
|
360
|
-
# OR for projects with custom .claude/settings.json:
|
|
361
|
-
cd /path/to/project && node dist/cli.js install-hooks --project
|
|
359
|
+
### Project Structure (30 source files)
|
|
362
360
|
|
|
363
|
-
# 7. Start daemon (choose one)
|
|
364
|
-
node dist/cli.js start # Foreground (for testing)
|
|
365
|
-
node dist/cli.js service install && \
|
|
366
|
-
node dist/cli.js service start # As system service (recommended)
|
|
367
361
|
```
|
|
368
|
-
|
|
369
|
-
|
|
362
|
+
rust-crates/ctm/src/
|
|
363
|
+
main.rs # CLI entry point (clap)
|
|
364
|
+
lib.rs # Library re-exports
|
|
365
|
+
hook.rs # Hook event processing
|
|
366
|
+
config.rs # Configuration loading (env > file > defaults)
|
|
367
|
+
error.rs # Centralized error types (thiserror)
|
|
368
|
+
types.rs # Shared types, validation, security constants
|
|
369
|
+
session.rs # SQLite session management
|
|
370
|
+
socket.rs # Unix socket server/client (flock, NDJSON)
|
|
371
|
+
injector.rs # tmux input injection
|
|
372
|
+
formatting.rs # Message formatting, chunking, ANSI stripping
|
|
373
|
+
summarize.rs # Tool action summarizer (30+ patterns)
|
|
374
|
+
colors.rs # ANSI color helpers for terminal output
|
|
375
|
+
doctor.rs # Diagnostic checks with --fix
|
|
376
|
+
installer.rs # Hook installer
|
|
377
|
+
setup.rs # Interactive setup wizard
|
|
378
|
+
bot/ # Telegram API client (client.rs, queue.rs, types.rs)
|
|
379
|
+
daemon/ # Bridge daemon (mod.rs, event_loop.rs, socket_handlers.rs,
|
|
380
|
+
# telegram_handlers.rs, callback_handlers.rs, cleanup.rs, files.rs)
|
|
381
|
+
service/ # OS service management (mod.rs, systemd.rs, launchd.rs, env.rs)
|
|
382
|
+
|
|
383
|
+
rust-crates/ctm/tests/ # 10 integration test files
|
|
384
|
+
```
|
|
370
385
|
|
|
371
386
|
</details>
|
|
372
387
|
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Threat Model
|
|
4
|
+
|
|
5
|
+
The claude-telegram-mirror bridges a local Claude Code CLI session to a remote
|
|
6
|
+
Telegram chat. The system has five trust boundaries, shown below.
|
|
7
|
+
|
|
8
|
+
```mermaid
|
|
9
|
+
graph TB
|
|
10
|
+
subgraph "Untrusted Network"
|
|
11
|
+
TG["Telegram Bot API<br/>(HTTPS, external)"]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
subgraph "Local Machine — Same User"
|
|
15
|
+
BOT["Bot Process<br/>(reqwest long-poll)"]
|
|
16
|
+
DAEMON["Bridge Daemon"]
|
|
17
|
+
SOCK["Unix Domain Socket<br/>(bridge.sock, 0o600)"]
|
|
18
|
+
HOOKS["Hook Handler<br/>(ctm hook subcommand)"]
|
|
19
|
+
TMUX["tmux Sessions<br/>(send-keys injection)"]
|
|
20
|
+
FS["File System<br/>(~/.config/claude-telegram-mirror/)"]
|
|
21
|
+
DB["SQLite DB<br/>(sessions.db, 0o600)"]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
TG -- "HTTPS poll" --> BOT
|
|
25
|
+
BOT -- "in-process" --> DAEMON
|
|
26
|
+
DAEMON -- "listen/accept" --> SOCK
|
|
27
|
+
HOOKS -- "connect/send NDJSON" --> SOCK
|
|
28
|
+
DAEMON -- "Command::new" --> TMUX
|
|
29
|
+
DAEMON -- "read/write" --> FS
|
|
30
|
+
DAEMON -- "rusqlite" --> DB
|
|
31
|
+
HOOKS -- "stdin pipe" --> HOOKS
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Trust boundaries
|
|
35
|
+
|
|
36
|
+
| Boundary | Trust level | Threat |
|
|
37
|
+
|----------|-------------|--------|
|
|
38
|
+
| Telegram Bot API to Bot process | Untrusted network | Spoofed updates, message injection from unauthorized chats |
|
|
39
|
+
| Unix domain socket (bridge.sock) | Same-user local IPC | Other local users or processes connecting |
|
|
40
|
+
| tmux send-keys | Same-user process control | Command injection via unsanitized text |
|
|
41
|
+
| File system (config dir) | Same-user file access | World-readable secrets, path traversal |
|
|
42
|
+
| Hook scripts (stdin) | Subprocess execution | Oversized payloads, malformed JSON |
|
|
43
|
+
|
|
44
|
+
## Security Mitigations
|
|
45
|
+
|
|
46
|
+
### 1. No Shell Interpolation in tmux Injection
|
|
47
|
+
|
|
48
|
+
**File:** `rust-crates/ctm/src/injector.rs`
|
|
49
|
+
|
|
50
|
+
All tmux commands use `Command::new("tmux")` with `.arg()` chains. The process
|
|
51
|
+
binary is the first argument and all subsequent arguments are passed directly to
|
|
52
|
+
the kernel without shell interpretation. The `-l` (literal) flag on `send-keys`
|
|
53
|
+
ensures tmux treats the injected string as literal keystrokes. No escaping or
|
|
54
|
+
quoting is needed.
|
|
55
|
+
|
|
56
|
+
Dead code for FIFO and PTY injection methods has been removed. The only
|
|
57
|
+
injection method is `tmux`. See [ADR-004](docs/adr/ADR-004-tmux-only-injection.md).
|
|
58
|
+
|
|
59
|
+
Slash commands (e.g., `/clear`, `/rename`) are validated against a character
|
|
60
|
+
whitelist (`[a-zA-Z0-9_- /]`) before injection. Commands containing shell
|
|
61
|
+
metacharacters are rejected.
|
|
62
|
+
|
|
63
|
+
### 2. Bot Token Scrubbing
|
|
64
|
+
|
|
65
|
+
**File:** `rust-crates/ctm/src/bot/client.rs`
|
|
66
|
+
|
|
67
|
+
A `tracing` subscriber layer applies `scrub_bot_token()` to log output.
|
|
68
|
+
The regex `bot\d+:[A-Za-z0-9_-]+/` matches the Telegram bot token pattern
|
|
69
|
+
in API URLs and replaces it with `bot<REDACTED>`.
|
|
70
|
+
|
|
71
|
+
All log output goes to stderr via the `tracing` subscriber. There is no
|
|
72
|
+
file transport, so tokens cannot leak into log files on disk.
|
|
73
|
+
|
|
74
|
+
The error handler in `rust-crates/ctm/src/bot/client.rs` also scrubs bot
|
|
75
|
+
tokens from error messages before logging.
|
|
76
|
+
|
|
77
|
+
### 3. Chat Authorization (Anti-IDOR)
|
|
78
|
+
|
|
79
|
+
**File:** `rust-crates/ctm/src/daemon/telegram_handlers.rs`
|
|
80
|
+
|
|
81
|
+
A chat ID check verifies `chat.id` against the configured `chat_id` on
|
|
82
|
+
every incoming update. Updates from unauthorized chats receive a static
|
|
83
|
+
"Unauthorized" reply and are not processed further.
|
|
84
|
+
|
|
85
|
+
Approval callback handlers (`approve:`, `reject:`, `abort:`), answer
|
|
86
|
+
handlers (`answer:`, `toggle:`, `submit:`), all verify the chat ID
|
|
87
|
+
matches the configured `chat_id` before processing. This prevents
|
|
88
|
+
IDOR attacks where a user who knows an approval ID could respond from a
|
|
89
|
+
different chat.
|
|
90
|
+
|
|
91
|
+
### 4. Session ID Validation
|
|
92
|
+
|
|
93
|
+
**File:** `rust-crates/ctm/src/daemon/socket_handlers.rs`
|
|
94
|
+
|
|
95
|
+
Session IDs from hook events are validated before any database operation:
|
|
96
|
+
- Maximum length: 128 characters
|
|
97
|
+
- Character set: `[a-zA-Z0-9_-]` only
|
|
98
|
+
- Empty/null values are rejected
|
|
99
|
+
|
|
100
|
+
Messages with invalid session IDs are dropped with a warning log.
|
|
101
|
+
|
|
102
|
+
### 5. Socket Security
|
|
103
|
+
|
|
104
|
+
**File:** `rust-crates/ctm/src/socket.rs`
|
|
105
|
+
|
|
106
|
+
The socket server enforces three limits:
|
|
107
|
+
- **NDJSON line limit:** 1 MiB (1,048,576 bytes) per line. Oversized lines
|
|
108
|
+
are dropped and logged.
|
|
109
|
+
- **Connection limit:** 64 concurrent connections. New connections beyond
|
|
110
|
+
this limit are destroyed immediately.
|
|
111
|
+
- **Directory permissions:** The socket directory is created with mode 0o700
|
|
112
|
+
(owner-only) and the socket file is set to 0o600 after binding.
|
|
113
|
+
|
|
114
|
+
A PID file lock prevents multiple daemon instances from racing on the same
|
|
115
|
+
socket.
|
|
116
|
+
|
|
117
|
+
### 6. Socket Path Validation
|
|
118
|
+
|
|
119
|
+
**File:** `rust-crates/ctm/src/config.rs`
|
|
120
|
+
|
|
121
|
+
`validateSocketPath()` rejects socket paths that:
|
|
122
|
+
- Contain `..` (directory traversal)
|
|
123
|
+
- Are not absolute (do not start with `/`)
|
|
124
|
+
- Exceed 256 characters
|
|
125
|
+
|
|
126
|
+
Invalid paths fall back to the default socket path in the config directory.
|
|
127
|
+
|
|
128
|
+
### 7. Config Directory Permissions
|
|
129
|
+
|
|
130
|
+
**File:** `rust-crates/ctm/src/config.rs`
|
|
131
|
+
|
|
132
|
+
`ensure_config_dir()` creates `~/.config/claude-telegram-mirror/` with mode
|
|
133
|
+
0o700. If the directory already exists, it enforces 0o700 via
|
|
134
|
+
`fs::set_permissions()`. All config directory creation goes through this
|
|
135
|
+
single function.
|
|
136
|
+
|
|
137
|
+
### 8. Database File Permissions
|
|
138
|
+
|
|
139
|
+
**File:** `rust-crates/ctm/src/session.rs`
|
|
140
|
+
|
|
141
|
+
Immediately after opening the SQLite database, `fs::set_permissions(db_path, 0o600)`
|
|
142
|
+
is called to ensure the database file is owner-readable/writable only.
|
|
143
|
+
|
|
144
|
+
### 9. Hook Stdin Size Limit
|
|
145
|
+
|
|
146
|
+
**File:** `rust-crates/ctm/src/hook.rs`
|
|
147
|
+
|
|
148
|
+
The hook handler reads stdin in chunks and enforces a 1 MiB
|
|
149
|
+
(1,048,576 byte) limit. If the accumulated input exceeds this limit, the
|
|
150
|
+
handler logs a warning and exits cleanly without processing the payload.
|
|
151
|
+
|
|
152
|
+
### 10. Download File Handling
|
|
153
|
+
|
|
154
|
+
**File:** `rust-crates/ctm/src/daemon/telegram_handlers.rs`
|
|
155
|
+
|
|
156
|
+
Downloaded files from Telegram are handled with several protections:
|
|
157
|
+
- The downloads directory is created with mode 0o700
|
|
158
|
+
- Downloaded files are written with mode 0o600
|
|
159
|
+
- Filenames are sanitized: path separators replaced, `..` removed, dotfile
|
|
160
|
+
prefixes neutralized, length capped at 200 characters
|
|
161
|
+
- Every filename is prefixed with a UUID for uniqueness
|
|
162
|
+
- Files older than 24 hours are automatically cleaned up
|
|
163
|
+
- Telegram enforces a 20 MB file size limit at the API level
|
|
164
|
+
|
|
165
|
+
## File Permission Summary
|
|
166
|
+
|
|
167
|
+
| File/Directory | Mode | Rationale |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `~/.config/claude-telegram-mirror/` | 0o700 | Contains bot token in config, session database, socket |
|
|
170
|
+
| `~/.telegram-env` | 0o600 | Contains bot token and chat ID for shell sourcing |
|
|
171
|
+
| `config.json` | 0o600 | Contains bot token |
|
|
172
|
+
| `sessions.db` | 0o600 | Contains session metadata, approval records |
|
|
173
|
+
| `bridge.sock` | 0o600 | IPC socket, same-user access only |
|
|
174
|
+
| `bridge.pid` | default | PID lock file, no sensitive content |
|
|
175
|
+
| `downloads/` | 0o700 | User-uploaded files from Telegram |
|
|
176
|
+
| Downloaded files | 0o600 | User-uploaded content, owner-only |
|
|
177
|
+
|
|
178
|
+
## Input Validation Summary
|
|
179
|
+
|
|
180
|
+
| Boundary | Validation | Enforcement |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| Socket messages: session ID | 128 char max, `[a-zA-Z0-9_-]` | `socket_handlers.rs` — `is_valid_session_id()` |
|
|
183
|
+
| Socket lines | 1 MiB max per NDJSON line | `socket.rs` — `MAX_LINE_BYTES` |
|
|
184
|
+
| Socket connections | 64 concurrent max | `socket.rs` — `MAX_CONNECTIONS` |
|
|
185
|
+
| Hook stdin | 1 MiB max | `hook.rs` — `MAX_STDIN_BYTES` |
|
|
186
|
+
| Socket paths | No `..`, absolute only, 256 char max | `config.rs` — `validate_socket_path()` |
|
|
187
|
+
| Slash commands | Character whitelist: `[a-zA-Z0-9_- /]` | `injector.rs` — `is_valid_slash_command()` |
|
|
188
|
+
| Download filenames | Sanitized, UUID-prefixed, no `..`, 200 char max | `telegram_handlers.rs` — `sanitize_filename()` |
|
|
189
|
+
| Download file size | 20 MB max | Telegram Bot API server-side limit |
|
|
190
|
+
| Telegram chat ID | Exact match against configured `chat_id` | `telegram_handlers.rs` — chat ID check |
|
|
191
|
+
|
|
192
|
+
## Security Checklist for Contributors
|
|
193
|
+
|
|
194
|
+
Before modifying security-sensitive code, verify:
|
|
195
|
+
|
|
196
|
+
- [ ] **No shell interpolation.** All subprocess calls use `Command::new()`
|
|
197
|
+
with `.arg()` chains, never string concatenation into a shell command.
|
|
198
|
+
- [ ] **No hardcoded secrets.** Bot tokens come from environment variables or
|
|
199
|
+
the config file, never from source code.
|
|
200
|
+
- [ ] **File permissions enforced.** Any new file or directory in the config
|
|
201
|
+
directory uses 0o600 (files) or 0o700 (directories).
|
|
202
|
+
- [ ] **Input validated at boundary.** Any data arriving from the socket, stdin,
|
|
203
|
+
or Telegram is validated before use. Session IDs, file paths, and command
|
|
204
|
+
strings are checked against their respective whitelists.
|
|
205
|
+
- [ ] **Bot token not logged.** Any error message that might contain a URL is
|
|
206
|
+
passed through `scrub_bot_token()` before logging.
|
|
207
|
+
- [ ] **Chat ID checked.** Any new callback handler or message handler verifies
|
|
208
|
+
the chat ID matches the configured `chat_id`.
|
|
209
|
+
- [ ] **Tests pass.** Run `cargo test` and confirm no regressions.
|
|
210
|
+
- [ ] **No new file logging.** All log output goes to stderr. Do not add file
|
|
211
|
+
transports to the logger.
|
|
212
|
+
- [ ] **Path traversal blocked.** Any user-controlled path component is
|
|
213
|
+
validated to reject `..` and non-absolute paths.
|
|
214
|
+
|
|
215
|
+
## Responsible Disclosure
|
|
216
|
+
|
|
217
|
+
If you discover a security vulnerability in claude-telegram-mirror, please
|
|
218
|
+
report it responsibly:
|
|
219
|
+
|
|
220
|
+
1. **Do not** open a public GitHub issue for security vulnerabilities.
|
|
221
|
+
2. Email the maintainers at the address listed in `package.json`, or use
|
|
222
|
+
GitHub's private vulnerability reporting feature on the repository.
|
|
223
|
+
3. Include a description of the vulnerability, steps to reproduce, and the
|
|
224
|
+
potential impact.
|
|
225
|
+
4. Allow up to 90 days for a fix before public disclosure.
|
|
226
|
+
|
|
227
|
+
We will acknowledge receipt within 48 hours and aim to release a fix within
|
|
228
|
+
30 days of confirmation.
|