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
-**Telegram outbound media hardening** (`internal/telegram/media_path.go` + `internal/telegram/approver.go` + `internal/telegram/handler.go` + `internal/tool/send_message.go` + `cmd/odek/telegram.go`) — paths supplied to `MEDIA:...` prefixes or `send_message(file=...)` are resolved to an absolute path and verified against an allowlist (cwd, `~/.odek/media/`, system temp dir). On Unix the final component is opened with `O_NOFOLLOW` and `fstat`'d to avoid a symlink TOCTOU race; `filepath.EvalSymlinks` ensures the resolved path does not escape the allowlist. Additionally, well-known secret subtrees (`~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.odek` trust anchors, etc.) and any file whose basename starts with `.env` are rejected outright, and every outbound media upload now requires explicit user approval via `TelegramApprover.PromptMedia`, with an extra warning when the bot was launched from `$HOME` or `/`.
174
174
-**Telegram plan file size cap** (`internal/telegram/plan.go`) — plan files larger than 1 MiB are rejected by `ReadPlan` and `MostRecentPlan`, and `ListPlans` only reads the first 8 KiB for preview. This prevents a prompt-injected agent from causing an OOM via a multi-hundred-megabyte plan file.
175
175
-**Telegram log file permissions** (`internal/telegram/log.go`) — Telegram log files are created with `0600`, and `os.Chmod` hardens existing files. Chat IDs and task snippets are no longer world-readable by default.
176
+
-**Telegram chat-scoped sessions and plans** (`internal/telegram/session.go`, `internal/telegram/plan.go`, `cmd/odek/telegram.go`) — `/sessions`, `/resume`, `/prune`, `/plans`, `/plan_view`, `/plan_delete`, and `/plan_resume` are scoped to the requesting chat. Sessions are filtered by the `tg-<chatID>` prefix, `ResumeSession` rejects cross-chat IDs, and plans live under `~/.odek/plans/chat<chatID>/` so one chat cannot list, read, delete, or resume another chat's sessions/plans.
176
177
-**Session ID entropy + session-scoped auth tokens** (`internal/session/session.go`, `cmd/odek/serve.go`) — session IDs now carry 128 bits of randomness (16 bytes / 32 hex chars); each session stores a 256-bit `AuthToken` required by `GET/DELETE/POST /api/sessions/<id>`, `POST /api/cancel`, and WebSocket session-resume messages via `X-Session-Token` header, `session_token` cookie, or `auth_token` WS field. Per-IP rate limiting (60/min) on session lookups adds a brute-force backstop.
177
178
-**Skill/episode untrusted wrapper** (`internal/loop/loop.go` + `odek.go`) — skill context and retrieved session-episode context are passed through the caller-provided untrusted wrapper (the same nonce'd `<untrusted_content_*>` boundary used for tool output) before being injected into the model's system context. This prevents a compromised or tainted skill/episode from being treated as trusted system instructions.
178
179
-**`env` / `printenv` environment-dump gate** (`internal/danger/classifier.go`) — bare `env` and `printenv` invocations are classified as `system_write` because they can leak process-environment secrets that the redaction scanner does not recognise. `env VAR=value <cmd>` still classifies `<cmd>` normally.
Copy file name to clipboardExpand all lines: docs/SECURITY.md
+12Lines changed: 12 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -543,6 +543,18 @@ Plan files live in `~/.odek/plans/` and are loaded by `/plan_view` and injected
543
543
544
544
Telegram log files were created with world-readable `0644` permissions, exposing chat IDs and task snippets to other local users. `NewFileLogger` now creates log files with `0600` and `os.Chmod`'s existing files to the same mode.
545
545
546
+
### 39c. Telegram chat-scoped sessions and plans
547
+
548
+
The Telegram bot's `/sessions`, `/resume`, `/prune`, `/plans`, `/plan_view`, `/plan_delete`, and `/plan_resume` commands previously operated on the global `~/.odek/sessions` and `~/.odek/plans` stores that are shared with the CLI. Any allowed chat could list the operator's CLI sessions (including task snippets that often contain secrets), resume one so its history entered the attacker's chat context, prune the operator's history, or read/delete plans created by other chats.
549
+
550
+
These commands are now scoped to the requesting chat:
551
+
552
+
- Each Telegram chat owns sessions with IDs of the form `tg-<chatID>` (and timestamped archives `tg-<chatID>-<YYYYMMDD>-<HHMMSS>`). `ListSessions`, `ResumeSession`, and `PruneSessions` only consider sessions whose ID starts with the caller's `tg-<chatID>` prefix.
553
+
-`ResumeSession` explicitly rejects a direct ID that belongs to a different chat.
554
+
- Plans are stored under `~/.odek/plans/chat<chatID>/`. `ListPlans`, `ReadPlan`, `DeletePlan`, and `MostRecentPlan` only look inside the caller's per-chat directory. Chat ID `0` is reserved as a global/admin scope mapping to the root `~/.odek/plans/` directory.
555
+
556
+
This removes the cross-chat session/plan disclosure path while keeping the CLI and admin flows functional.
557
+
546
558
### 40. `/api/resources` result limit cap
547
559
548
560
The `/api/resources?q=...&limit=N` autocomplete endpoint previously accepted any positive `limit` value. It is now capped to 100 results both in the HTTP handler and in `Registry.Search`. This prevents a prompt-injected or attacker-forged request from forcing an unbounded directory walk and returning a multi-megabyte JSON response.
Copy file name to clipboardExpand all lines: docs/TELEGRAM.md
+13-12Lines changed: 13 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -231,12 +231,12 @@ The agent can send files back to the chat either by emitting a `MEDIA:` prefix i
231
231
|`/mode`| Show current agent modes (interaction_mode, sandbox, skills) |
232
232
|`/restart`| Gracefully restart the bot process. Restricted to operator chats/users and rate-limited to once per 60 seconds. |
233
233
|`/plan <description>`| Create a new plan from a natural language description |
234
-
|`/plans`| List all saved plans |
235
-
|`/plan-view <slug>`| View a specific plan's content |
236
-
|`/plan-delete <slug>`| Delete a saved plan |
237
-
|`/sessions`| List recent conversation sessions |
238
-
|`/resume <session_id>`| Resume a previous session by ID|
239
-
|`/prune [days]`| Clean up old sessions (default: 30 days) |
234
+
|`/plans`| List saved plans for this chat|
235
+
|`/plan-view <slug>`| View a specific plan's content for this chat |
236
+
|`/plan-delete <slug>`| Delete a saved plan for this chat |
237
+
|`/sessions`| List recent conversation sessions for this chat |
238
+
|`/resume <session_id>`| Resume a previous session owned by this chat|
239
+
|`/prune [days]`| Clean up old sessions and plans for this chat (default: 30 days) |
240
240
|`/schedules`| List scheduled tasks (id, on/off, cron, next fire, last status) |
241
241
|`/schedule <subcommand>`| Manage scheduled tasks — `add`, `rm`, `enable`, `disable`, `run`, `next`, `view`. Mutating commands are restricted to configured operator chats/users. See [Managing schedules from Telegram](SCHEDULES.md#managing-from-telegram)|
242
242
@@ -262,6 +262,7 @@ The `SessionManager` manages per-chat Telegram agent conversations, backed by th
262
262
5.**Session recall** — the user message is saved to the session store *before* the agent loop runs, enabling `session_search` to find the current conversation's data during the same turn
263
263
6. Active sessions survive bot restarts — on reconnect, the session is loaded from disk
264
264
7.**/now archives** — using `/new` archives the current session with a timestamped ID (`tg-<chatID>-<YYYYMMDD>-<HHMMSS>`) before starting fresh. Archived sessions remain on disk and are visible via `odek session list`.
265
+
8.**Chat-scoped listing/resume/prune** — `/sessions`, `/resume`, and `/prune` only operate on sessions belonging to the requesting chat. A chat cannot list, resume, or delete another chat's sessions.
265
266
266
267
### Key Methods
267
268
@@ -278,7 +279,7 @@ The `clarifyChannels` sync.Map provides per-chat channels for the agent to ask t
278
279
279
280
## Plan Management (`plan.go`)
280
281
281
-
Plans are stored as markdown files in `~/.odek/plans/<slug>.md`. Each plan is created from a natural language description and persisted for later review.
282
+
Plans are stored as markdown files in `~/.odek/plans/chat<chatID>/<slug>.md`, isolating each Telegram chat's plans. Each plan is created from a natural language description and persisted for later review. The `/plan` command instructs the agent to save plans into the caller's per-chat directory, and `/plans`, `/plan_view`, `/plan_delete`, and `/plan_resume` only see plans belonging to that chat.
282
283
283
284
### Size Cap
284
285
@@ -292,11 +293,11 @@ To prevent a prompt-injected agent from causing an out-of-memory condition, plan
292
293
| Function | Purpose |
293
294
|---|---|
294
295
|`Slugify(description)`| Convert description to filesystem-safe slug (max 60 chars) |
295
-
|`ListPlans(limit)`| List plans sorted by modification time (newest first) |
296
-
|`ReadPlan(slug)`| Load plan content by exact slug or prefix match |
297
-
|`DeletePlan(slug)`| Delete plan by exact slug or prefix match |
298
-
|`MostRecentPlan()`| Return the most recently modified plan's content |
299
-
|`EnsurePlansDir()`| Create plans directory if it doesn't exist |
296
+
|`ListPlans(chatID, limit)`| List plans for a chat sorted by modification time (newest first) |
297
+
|`ReadPlan(chatID, slug)`| Load a chat's plan content by exact slug or prefix match |
298
+
|`DeletePlan(chatID, slug)`| Delete a chat's plan by exact slug or prefix match |
299
+
|`MostRecentPlan(chatID)`| Return the most recently modified plan's content for a chat|
300
+
|`EnsurePlansDir(chatID)`| Create per-chat plans directory if it doesn't exist |
0 commit comments