You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: replace telegram PID-file singleton lock with flock
The PID-file lock probed liveness with signals and could kill arbitrary
processes on non-Linux systems if an attacker planted a victim PID in
~/.odek/telegram.pid. Replace it with an advisory flock on
~/.odek/telegram.lock via the existing internal/flock module.
- Update cmd/odek/telegram.go acquireLock/release to use flock.Lock.
- Remove PID read/kill/write logic and the instanceLock struct.
- Clean up legacy telegram.pid file on successful lock acquisition.
- Add regression tests for lock-file creation, legacy PID cleanup, and
no-kill behaviour.
- Update docs/TELEGRAM.md, docs/SECURITY.md, and AGENTS.md.
-**Project config sensitive-field rejection** (`internal/config/loader.go`) — `./odek.json` is untrusted, so `base_url`, `api_key`, `system`, and the `dangerous` section set there are ignored (with stderr warnings). These can only be configured from operator-controlled sources: `~/.odek/config.json`, `ODEK_*` env vars, or CLI flags.
136
136
-**MCP subprocess environment sanitisation** (`internal/mcpclient/client.go`) — MCP server children receive only a minimal allowlist of safe environment variables plus explicit `env` overrides. Keys matching secret patterns (`*_API_KEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, etc.) are stripped, preventing a compromised or malicious MCP server from reading parent secrets.
137
137
-**Schedule atomic-write hardening** (`internal/schedule/store.go` + `internal/fsatomic`) — schedule file writes now use `fsatomic.WriteFile`, which creates a random temp file with `O_EXCL`, fsyncs data and directory, and renames over the target. A swapped-in symlink is replaced rather than followed, closing the symlink-override attack on `schedules.json` / `schedule-state.json`.
138
+
-**Telegram singleton flock lock** (`cmd/odek/telegram.go` + `internal/flock`) — the Telegram bot now uses an advisory `flock` on `~/.odek/telegram.lock` instead of a PID file probed with signals. This removes the non-Linux path where a planted PID could cause odek to kill an arbitrary process.
138
139
-**Telegram photo caption wrapping** (`cmd/odek/telegram.go`) — photo captions cross the Telegram trust boundary, so they are wrapped as untrusted content both when passed to the local vision model and when injected into the main agent's user message.
139
140
-**Secret redaction** (`internal/redact/redact.go`) — 20+ patterns: OpenAI, Anthropic, GitHub PAT, AWS, PEM, JWT, Vault, Google OAuth, SendGrid, Discord, DB URLs, etc.
Copy file name to clipboardExpand all lines: docs/SECURITY.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -316,6 +316,10 @@ MCP server subprocesses no longer inherit the full odek process environment. The
316
316
317
317
Schedule persistence (`schedules.json` and `schedule-state.json`) now writes through `internal/fsatomic.WriteFile`. It creates a uniquely-named temp file with `O_EXCL` (so a pre-created symlink cannot be opened), fsyncs the data and parent directory, and atomically renames over the target. This means a swapped-in symlink is replaced rather than followed, closing the symlink-override attack where an attacker points `schedules.json.tmp` or `schedule-state.json.tmp` at sensitive files.
The Telegram bot previously used a PID file at `~/.odek/telegram.pid` to enforce a single polling instance. On Linux it verified `/proc/<pid>/cmdline`, but on macOS and other POSIX systems it would kill whatever process the planted PID belonged to. The implementation now uses an advisory `flock` on `~/.odek/telegram.lock` via `internal/flock`. A second instance simply blocks until the first releases the lock, and the OS releases the lock automatically if the holder crashes, eliminating the arbitrary-process-kill vector.
Copy file name to clipboardExpand all lines: docs/TELEGRAM.md
+6-5Lines changed: 6 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -415,7 +415,7 @@ The package defines Telegram API types used throughout:
415
415
416
416
### Singleton Lock
417
417
418
-
The bot writes its PID to `~/.odek/telegram.pid` on startup. If a stale PID file exists from a previous instance, the new process kills it (SIGTERM → 5s grace → SIGKILL) before taking over. This prevents 409 Conflict errors from dual polling.
418
+
The bot acquires an advisory file lock on `~/.odek/telegram.lock` on startup. If another instance is already running, the new process blocks on the lock until the old process exits, then takes over automatically. This prevents 409 Conflict errors from dual polling without trusting or killing PID values, which could otherwise be planted to target unrelated processes.
419
419
420
420
### Graceful Restart
421
421
@@ -445,9 +445,9 @@ During restart:
445
445
446
446
3.**New messages are rejected** — any message arriving while restart is in progress gets "⏳ Bot is restarting — please try again in a few seconds." The message is not lost (it remains in the Telegram server).
447
447
448
-
4.**Bounded drain** — the process waits up to 15 seconds for all agent goroutines to finish. If a task is stuck (e.g., a long HTTP call that ignores context), the child process takes over and the parent is killed by the singleton lock.
448
+
4.**Bounded drain** — the process waits up to 15 seconds for all agent goroutines to finish. If a task is stuck (e.g., a long HTTP call that ignores context), the child process takes over after the parent releases the singleton lock.
449
449
450
-
5.**PID file cleanup** — before `os.Exit(0)`, the PID file lock is explicitly released so the child process starts with no stale lock file.
450
+
5.**Lock release** — before `os.Exit(0)`, the singleton lock is explicitly released so the child process can acquire it immediately.
451
451
452
452
6.**Post-restart notification** — when the new instance starts, it reads the restart marker file and sends "🔄 Bot restarted" to each chat that was active during the restart.
453
453
@@ -458,12 +458,13 @@ The actual process handoff uses the same spawn+exit mechanism:
The child process inherits environment variables and command-line arguments. `acquireLock`ensures the old process is dead before the new one starts polling. The restart marker at `~/.odek/restart.json` carries the list of chat IDs that had active agent runs.
467
+
The child process inherits environment variables and command-line arguments. `acquireLock`waits for the parent to release the lock, then the child starts polling. The restart marker at `~/.odek/restart.json` carries the list of chat IDs that had active agent runs.
467
468
468
469
This avoids binary overwrite races, stale HTTP/2 connections, and session context loops that plagued `syscall.Exec`. The restart marker (`~/.odek/restart.json`) enables the new instance to notify users that a restart occurred.
0 commit comments