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: close H8 batch-approval classifyToolCall gaps and Telegram class-trust guard (M-1)
- classifyToolCall now handles parallel_shell commands, batch_patch paths, and the modern browser tool.
- Batch approval card lists every classified command/path and shows full text instead of truncating.
- Iterations containing unclassifiable tools no longer receive blanket SetTrustAll.
- Telegram approver hides Trust Session for destructive/blocked/unknown/tool_batch and denies trust callbacks for those classes.
- Update AGENTS.md and docs/SECURITY.md; add regression tests.
-**Batch approval gate** — multiple risky tools shown at once in a single prompt.
87
+
-**Batch approval gate**(`internal/loop/loop.go`) — multiple risky tools shown at once in a single prompt. `classifyToolCall` now classifies every command inside `parallel_shell`, every path inside `batch_patch`, and the modern `browser` tool, shows full (untruncated) commands, and withholds blanket `SetTrustAll` when unclassifiable tools remain in the iteration.
88
88
-**Tool-failure recovery** — systematic recovery from tool call failures: retry transient errors, skip permanently failed tools, and continue the loop without crashing.
89
89
-**Context-limit protection** — `trimToSurvival` drops oldest messages when approaching the model's context window, keeping the agent functional under extended sessions. Tool messages stay grouped with their parent assistant message.
-**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.
170
170
-**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.
171
171
-**`send_message` callback prefix restriction** (`internal/tool/send_message.go` + `cmd/odek/telegram.go`) — the `send_message` tool rejects any button whose `callback_data` starts with a reserved internal prefix (`apr:`, `den:`, `trs:`, `clarify:`, `skill_save:`, `skill_skip:`); only user-facing `cb:` callbacks are allowed. The Telegram sender closure validates again as defense-in-depth, preventing a forged approval or skill button.
172
+
-**Telegram class-trust guard** (`internal/telegram/approver.go`) — the "Trust Session" button is hidden for `destructive`, `blocked`, `unknown`, and the synthetic `tool_batch` class, and a trust callback for those classes is treated as a denial. This matches the TTY/Web approver policy and prevents one batch approval from blanket-authorizing later destructive operations.
172
173
-**Telegram outbound media path allowlist** (`internal/telegram/media_path.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, preventing prompt-injection-driven exfiltration of arbitrary files.
173
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.
174
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.
Copy file name to clipboardExpand all lines: docs/SECURITY.md
+17Lines changed: 17 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -496,6 +496,23 @@ It now uses `exec.CommandContext` with the agent context, runs each command in i
496
496
497
497
`write_file` and `patch` pass their cached `trustedClasses` to `CheckOperation`, but `batch_patch` was passing `nil`. This meant a user who trusted `local_write` for the session still got re-prompted (or denied in non-interactive mode) for every patch in a batch. `batch_patch` now has its own `trustedClasses` field and passes it through, giving consistent approval behavior across the file-editing tools.
498
498
499
+
### 35a. Batch approval card shows all classifiable commands
500
+
501
+
The batch approval gate in `internal/loop/loop.go::classifyToolCall` only understood flat `command`/`path` arguments, so `parallel_shell` (an array of commands), `batch_patch` (an array of paths), and the modern `browser` tool were invisible in the approval card. After one tap on Approve, `SetTrustAll(true)` let hidden payloads run without further prompting.
502
+
503
+
`classifyToolCall` now:
504
+
505
+
- Walks every command inside `parallel_shell` and every path inside `batch_patch`, classifies each one, and lists all of them in the batch card.
506
+
- Recognises the modern `browser` tool (action + URL) as `network_egress`.
507
+
- Shows the full command/path text instead of truncating at 120 characters, so the user sees exactly what is being authorized.
508
+
- Refuses to grant blanket trust (`SetTrustAll`) for any iteration that still contains an unclassifiable tool; those tools must be approved individually by their own internal gates.
509
+
510
+
### 35b. Telegram class-trust guard
511
+
512
+
`internal/telegram/approver.go` previously offered a "Trust Session" button for every risk class, including `destructive`, `blocked`, `unknown`, and the synthetic `tool_batch` class. One tap on Trust Session cached approval for that class, so trusting a benign `tool_batch` card also silently approved every later destructive operation in the same session.
513
+
514
+
The Telegram approver now mirrors the TTY/Web policy: the Trust Session button is hidden for `destructive`, `blocked`, `unknown`, and `tool_batch`, and a trust callback for those classes is treated as a denial. This closes the approval-fatigue path where an injected batch of mixed-risk tools is reduced to a single tap.
515
+
499
516
### 36. Browser link URL wrapping
500
517
501
518
`browser` already wrapped page title, content, and interactive-element text as untrusted, but the `URL` field of each `clickableRef` was emitted as a raw JSON string. A hostile page could set `href` to a `javascript:`, `data:`, or attacker-controlled URL containing instruction-like text. The `URL` field is now wrapped as untrusted before serialization. An unexported `rawURL` preserves the original value so internal click resolution continues to work.
0 commit comments