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.
Files changed (95) hide show
  1. package/README.md +106 -91
  2. package/SECURITY.md +228 -0
  3. package/package.json +14 -37
  4. package/postinstall.cjs +44 -40
  5. package/scripts/ctm-wrapper.cjs +80 -0
  6. package/scripts/resolve-binary.cjs +213 -0
  7. package/dist/bot/commands.d.ts +0 -41
  8. package/dist/bot/commands.d.ts.map +0 -1
  9. package/dist/bot/commands.js +0 -240
  10. package/dist/bot/commands.js.map +0 -1
  11. package/dist/bot/formatting.d.ts +0 -62
  12. package/dist/bot/formatting.d.ts.map +0 -1
  13. package/dist/bot/formatting.js +0 -295
  14. package/dist/bot/formatting.js.map +0 -1
  15. package/dist/bot/telegram.d.ts +0 -105
  16. package/dist/bot/telegram.d.ts.map +0 -1
  17. package/dist/bot/telegram.js +0 -458
  18. package/dist/bot/telegram.js.map +0 -1
  19. package/dist/bot/types.d.ts +0 -28
  20. package/dist/bot/types.d.ts.map +0 -1
  21. package/dist/bot/types.js +0 -5
  22. package/dist/bot/types.js.map +0 -1
  23. package/dist/bridge/daemon.d.ts +0 -159
  24. package/dist/bridge/daemon.d.ts.map +0 -1
  25. package/dist/bridge/daemon.js +0 -1083
  26. package/dist/bridge/daemon.js.map +0 -1
  27. package/dist/bridge/index.d.ts +0 -10
  28. package/dist/bridge/index.d.ts.map +0 -1
  29. package/dist/bridge/index.js +0 -9
  30. package/dist/bridge/index.js.map +0 -1
  31. package/dist/bridge/injector.d.ts +0 -112
  32. package/dist/bridge/injector.d.ts.map +0 -1
  33. package/dist/bridge/injector.js +0 -354
  34. package/dist/bridge/injector.js.map +0 -1
  35. package/dist/bridge/session.d.ts +0 -134
  36. package/dist/bridge/session.d.ts.map +0 -1
  37. package/dist/bridge/session.js +0 -440
  38. package/dist/bridge/session.js.map +0 -1
  39. package/dist/bridge/socket.d.ts +0 -97
  40. package/dist/bridge/socket.d.ts.map +0 -1
  41. package/dist/bridge/socket.js +0 -436
  42. package/dist/bridge/socket.js.map +0 -1
  43. package/dist/bridge/types.d.ts +0 -38
  44. package/dist/bridge/types.d.ts.map +0 -1
  45. package/dist/bridge/types.js +0 -5
  46. package/dist/bridge/types.js.map +0 -1
  47. package/dist/cli.d.ts +0 -7
  48. package/dist/cli.d.ts.map +0 -1
  49. package/dist/cli.js +0 -576
  50. package/dist/cli.js.map +0 -1
  51. package/dist/hooks/handler.d.ts +0 -106
  52. package/dist/hooks/handler.d.ts.map +0 -1
  53. package/dist/hooks/handler.js +0 -463
  54. package/dist/hooks/handler.js.map +0 -1
  55. package/dist/hooks/index.d.ts +0 -8
  56. package/dist/hooks/index.d.ts.map +0 -1
  57. package/dist/hooks/index.js +0 -7
  58. package/dist/hooks/index.js.map +0 -1
  59. package/dist/hooks/installer.d.ts +0 -56
  60. package/dist/hooks/installer.d.ts.map +0 -1
  61. package/dist/hooks/installer.js +0 -443
  62. package/dist/hooks/installer.js.map +0 -1
  63. package/dist/hooks/types.d.ts +0 -94
  64. package/dist/hooks/types.d.ts.map +0 -1
  65. package/dist/hooks/types.js +0 -6
  66. package/dist/hooks/types.js.map +0 -1
  67. package/dist/index.d.ts +0 -19
  68. package/dist/index.d.ts.map +0 -1
  69. package/dist/index.js +0 -20
  70. package/dist/index.js.map +0 -1
  71. package/dist/service/doctor.d.ts +0 -10
  72. package/dist/service/doctor.d.ts.map +0 -1
  73. package/dist/service/doctor.js +0 -424
  74. package/dist/service/doctor.js.map +0 -1
  75. package/dist/service/manager.d.ts +0 -52
  76. package/dist/service/manager.d.ts.map +0 -1
  77. package/dist/service/manager.js +0 -596
  78. package/dist/service/manager.js.map +0 -1
  79. package/dist/service/setup.d.ts +0 -11
  80. package/dist/service/setup.d.ts.map +0 -1
  81. package/dist/service/setup.js +0 -628
  82. package/dist/service/setup.js.map +0 -1
  83. package/dist/utils/chunker.d.ts +0 -24
  84. package/dist/utils/chunker.d.ts.map +0 -1
  85. package/dist/utils/chunker.js +0 -123
  86. package/dist/utils/chunker.js.map +0 -1
  87. package/dist/utils/config.d.ts +0 -51
  88. package/dist/utils/config.d.ts.map +0 -1
  89. package/dist/utils/config.js +0 -160
  90. package/dist/utils/config.js.map +0 -1
  91. package/dist/utils/logger.d.ts +0 -7
  92. package/dist/utils/logger.d.ts.map +0 -1
  93. package/dist/utils/logger.js +0 -28
  94. package/dist/utils/logger.js.map +0 -1
  95. package/scripts/telegram-hook.sh +0 -423
package/README.md CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/claude-telegram-mirror.svg)](https://www.npmjs.com/package/claude-telegram-mirror)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ ![Rust](https://img.shields.io/badge/Built_with-Rust-dea584.svg)
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
- The setup wizard guides you through:
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
- - **Stop/Interrupt**: Type `stop` in Telegram to send Ctrl+C and halt Claude mid-process
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:** The top-level `ctm stop` and `ctm restart` commands work for both direct daemon mode and OS service mode. They automatically detect how the daemon is running and use the appropriate method.
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
- See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for additional command aliases.
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
- **Note:** Approval buttons only appear when running Claude in normal mode. They do not appear when using `--dangerously-skip-permissions` mode. If you don't respond within 5 minutes, Claude will fall back to asking for approval in the CLI terminal.
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 │────▶│ Bridge Daemon │────▶│ Telegram │
112
- │ CLI │◀────│ │◀────│ Bot │
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
- PreToolUse: │────▶│ Socket Server │
119
- Node.js handler│◀────│ (bidirectional)│
120
- (with approval)│ │ │
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 capture events (prompts, responses, tool use)
130
- 2. PreToolUse: Node.js handler sends approval request, waits for Telegram response
131
- 3. Other hooks: Bash script sends JSON and exits immediately (faster)
132
- 4. Bridge forwards messages to Telegram Forum Topic
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 `Ctrl-C` to interrupt Claude
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) `/newbot`
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 Enable **Topics**
196
+ 3. Group Settings -> Enable **Topics**
189
197
 
190
198
  ### 3. Make Bot an Admin
191
199
 
192
- 1. Group Settings Administrators Add your bot
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. Run the helper script:
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) `/mybots` Select bot
208
- 2. Bot Settings Group Privacy **Turn off**
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: Node.js, config, hooks, socket, tmux, systemd, Telegram API
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 Telegram | User types | 👤 User (cli): ... |
272
- | CLI Telegram | Tool starts | 🔧 Running: Bash |
273
- | CLI Telegram | Claude responds | 🤖 Claude: ... |
274
- | CLI Telegram | Session starts | New Forum Topic created |
275
- | CLI Telegram | Context compacting | Notification sent |
276
- | Telegram CLI | User sends message | Injected via tmux |
277
- | Telegram CLI | User types "stop" | Sends Ctrl+C interrupt |
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
- - **Compaction alerts**: PreCompact hook sends notification before context summarization
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: `pkill -f "claude-telegram-mirror"`
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
- - Enable debug: `export TELEGRAM_HOOK_DEBUG=1` then retry
313
- - Check debug log: `cat ~/.config/claude-telegram-mirror/hook-debug.log`
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
- ## Manual Setup (for developers)
337
+ ## Build from Source
333
338
 
334
339
  <details>
335
- <summary>Click to expand manual installation steps</summary>
340
+ <summary>Click to expand</summary>
336
341
 
337
- For developers who want to work on the source code:
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 && npm install && npm run build
343
-
344
- # 2. Create a Telegram bot via @BotFather, get the token
347
+ cd claude-telegram-mirror/rust-crates
348
+ cargo build --release
349
+ # Binary at: rust-crates/target/release/ctm
345
350
 
346
- # 3. Create a supergroup with Topics enabled, add your bot as admin
351
+ # 2. Run tests
352
+ cargo test
347
353
 
348
- # 4. Get your chat ID
349
- ./scripts/get-chat-id.sh YOUR_BOT_TOKEN
350
-
351
- # 5. Configure environment
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
- # 6. Install hooks
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
- **Note:** When using npm install, use `ctm` instead of `node dist/cli.js`.
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.