toolsrv: neutralize repo-controlled core.fsmonitor in tool env

GitSpawn class (Sep 2026): a repo's .git/config can set core.fsmonitor to
a command that executes on any git index refresh, silently and as the
user. Ollie never spawns git in its own plumbing (repo detection is
os.Stat), but model-run git inside a malicious repo would fire it.

- exec: force core.fsmonitor=false via GIT_CONFIG_* in sandboxed tool env
- turn: wrap repo AGENTS.md in <context> markers (untrusted data,
  filtered by KDE chat rendering)
- AGENTS.md: document the no-git-in-plumbing invariant (lesson 16)
This commit is contained in:
Ollie Agent 2026-09-05 11:42:38 +02:00
parent 989aa0400d
commit 3ca84487c5
3 changed files with 13 additions and 1 deletions

View File

@ -244,3 +244,5 @@ KDE integration is part of this repository under `kde/`. Build and install it th
14. **Separate index files for separate concerns.** Don't cram session and agent data into one line. `session/idx` lists sessions; `session/{s}/agent/idx` lists agents per session. Simpler parsing, fewer race conditions, cleaner code.
15. **Split large files by concern, not by size.** A 900-line file with mixed responsibilities is worse than three 300-line files with clear boundaries. Name files by what they do (`dispatch.go`, `compact.go`, `retry.go`), not by their parent type (`agent_dispatch.go`).
16. **Never shell out to git in agent plumbing.** A repo's own `.git/config` can name commands (`core.fsmonitor`, hooks, filters) that run as the user on any git invocation — silently, before any prompt (GitSpawn class, Sep 2026). Repo detection stays `os.Stat(.git)` (`util/paths.go`, `cmd/toolsrv/internal/server/proc.go`). If a feature needs git context, run `git -c core.fsmonitor=false ...` inside the sandbox, or read the files directly. Sandboxed tool env already forces `core.fsmonitor=false` via `GIT_CONFIG_*` (cmd/toolsrv/internal/exec/exec.go).

View File

@ -420,9 +420,12 @@ func (ag *Agent) autoWarnLimit(ctx context.Context) int {
func (ag *Agent) spawnContext(ctx context.Context) string {
var parts []string
// Inject AGENTS.md from the working directory if it exists.
// Repo-provided content is untrusted data: wrap it so consumers and the
// model can distinguish it from user/system instructions (KDE filters
// <context> blocks in chat rendering).
if cwd := ag.Cwd(); cwd != "" {
if data, err := os.ReadFile(cwd + "/AGENTS.md"); err == nil && len(data) > 0 {
parts = append(parts, string(data))
parts = append(parts, "<context>\n"+string(data)+"\n</context>")
}
}
return strings.Join(parts, "\n\n---\n\n")

View File

@ -158,6 +158,13 @@ func executeSandboxed(ctx context.Context, toolPath, stdinData, cwd string, envE
envMap["NAMESPACE"] = ns
}
}
// Neutralize repo-controlled git config (GitSpawn / core.fsmonitor):
// tools run with CWD inside a repository whose .git/config may name
// commands (fsmonitor) that execute on any index refresh. GIT_CONFIG_*
// acts like `-c core.fsmonitor=false` and overrides repo/global config.
envMap["GIT_CONFIG_COUNT"] = "1"
envMap["GIT_CONFIG_KEY_0"] = "core.fsmonitor"
envMap["GIT_CONFIG_VALUE_0"] = "false"
var cmd *osExec.Cmd
if yolo {