diff --git a/AGENTS.md b/AGENTS.md index f207b96..9d6766b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -165,6 +165,7 @@ Layered prompt-injection / approval-fatigue defenses. Full reference: [docs/SECU - **`parallel_shell` context + process-group kill** (`cmd/odek/perf_tools.go`) — commands now run via `exec.CommandContext` bound to the agent context, in their own process group. Cancellation or timeout kills the whole group (negative PID), so `sh -c 'sleep 3600 &'` cannot leave orphaned children. Per-command timeouts are also capped at 30 minutes. - **`batch_patch` trusted-class propagation** (`cmd/odek/perf_tools.go`) — `batch_patch` now passes its cached `trustedClasses` to `CheckOperation`, matching `write_file` and `patch`. A trusted `local_write` class is honored across all patches in the batch instead of re-prompting per patch. - **Browser link URL wrapping** (`cmd/odek/browser_tool.go`) — interactive element text was already wrapped as untrusted, but link URLs in `clickableRef.URL` were returned raw. They are now wrapped too, while an unexported `rawURL` is kept for internal click resolution. +- **Browser post-redirect URL attribution** (`cmd/odek/browser_tool.go`) — `browser_navigate` now uses `resp.Request.URL` (the final post-redirect URL) for the snapshot URL, the untrusted-content source, and relative-link click resolution, instead of attributing content to the original requested URL. - **Telegram message length by UTF-16 code units** (`internal/telegram/handler.go`) — `MaxMsgLength` is enforced using UTF-16 code-unit counting, matching Telegram's own limits. Multi-byte UTF-8 characters (e.g. emoji) no longer pass the local check while being rejected by Telegram. - **Telegram restart marker permissions** (`cmd/odek/telegram.go`) — `~/.odek/restart.json` is now written with `0600` instead of `0644`, preventing local users from reading the list of active chat IDs. - **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. diff --git a/cmd/odek/browser_tool.go b/cmd/odek/browser_tool.go index f881098..7755289 100644 --- a/cmd/odek/browser_tool.go +++ b/cmd/odek/browser_tool.go @@ -242,7 +242,12 @@ func (t *browserTool) doNavigate(rawURL string) (string, error) { } html := string(body) - snap := parseHTML(t.toolCtx(), html, rawURL, resp.StatusCode) + // Use the post-redirect URL for attribution and relative-link resolution. + finalURL := rawURL + if resp.Request != nil && resp.Request.URL != nil { + finalURL = resp.Request.URL.String() + } + snap := parseHTML(t.toolCtx(), html, finalURL, resp.StatusCode) // Store in state. Keep a persistent copy of the snapshot for current; the // local variable's address would otherwise escape to the heap implicitly. diff --git a/cmd/odek/browser_tool_test.go b/cmd/odek/browser_tool_test.go index 8e44224..6bd71b5 100644 --- a/cmd/odek/browser_tool_test.go +++ b/cmd/odek/browser_tool_test.go @@ -58,6 +58,69 @@ func TestBrowser_Navigate(t *testing.T) { } } +func TestBrowser_Navigate_FollowsRedirect(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/redirect": + http.Redirect(w, r, "/target", http.StatusFound) + case "/target": + w.Write([]byte(`