Execution Guards
Last updated
The write guard, argv-only subprocess schema and timeouts that sit alongside the sandbox.
Filesystem write guard
go-pkg/filesystem write APIs (WriteFile, WriteText, WriteJSON, AppendText, Copy, Move, Remove) all enforce IsDenied internally; CheckDir only creates directories and does not check it. Any agenvoy code that bypasses go-pkg/filesystem and writes via os.WriteFile directly escapes the policy — this is forbidden.The
internal/filesystem package retains only path computation, runtime limits, and domain wrappers. It does not duplicate read/write logic.Outside
$HOME, run_command and background_command need the paths declared up front: the call carries write_paths (absolute paths only), the user approves them with the system password, and only then are they bound read-write into the sandbox for that session. A write to an unapproved path fails with an explicit message, and a permission error on an unbound path outside $HOME gets a hint to re-run with write_paths rather than a bare error the model would misread as an ownership problem. Since v1.3.2, when a sandboxed command fails with a permission marker (permission denied, operation not permitted, read-only file system, EACCES, not writable) and the output names no path outside $HOME to grant through write_paths, the result appends advice that treats leaving the sandbox as the last resort, not the default retry (since v1.3.11; from v1.3.2 to v1.3.10 it told the agent to re-run at once with sandbox: false): a connection or auth failure such as Permission denied (publickey) or could not resolve host → network: true, or report the credential problem; a write → a path under the work directory, or write_paths for the path that failed; a task that genuinely needs a system socket, device or service, or a root-owned target → sandbox: false or argv ['sudo', ...], approved with the system password; otherwise report what was blocked and stop.Subprocess argv-only schema
run_command accepts only argv: string[] (minItems 1) plus optional write_paths, since v1.0.25 network (boolean, default off), and since v1.3.0 sandbox (boolean, default true; false runs outside the sandbox after a password check and ignores network / write_paths). background_command adds timeout_min (1-60, default 60) and keywords (the error keywords its output watch looks for) to the same set; run_command_background was renamed background_command and its interval and prompt parameters were dropped (Removed in v1.3.2). It does not accept a command: string with auto-tokenization. This zero-parsing approach removes shell-injection surface in the agent layer.At the top level, since v1.3.2
['sudo', '<binary>', ...] is accepted: no sudo flags, no nested sudo / su, and the target must be a bare binary not on denied_command; it raises a sudo: <cmd> confirmation that always asks for the system password and then runs as sudo -n <binary> ... outside the sandbox. Since v1.3.11 cancelling such a call (foreground or background_command) sends SIGTERM to sudo, which relays it to the root command, instead of SIGKILLing the process group, which a root child would survive; if it has not exited within the 2-second WaitDelay, sudo is then killed. rm is routed to the trash (sudo rm deletes for real); its targets follow the path tiers — inside the working directory it runs under auto, elsewhere in $HOME it asks, outside $HOME it needs the system password — and cd is rejected because the work directory is set by the user in the TUI; the agent cd that switched the work directory was Removed in v1.3.0.Shell features (pipes, redirects) require an explicit
["sh", "-c", "cmd | pipe"] (or bash -c). That script is parsed with mvdan.cc/sh rather than string-matched, and every command node inside it is checked:| Rule | Effect |
|---|---|
| Bare command names only | /usr/bin/curl is rejected — the binary must be written as curl |
| No dynamic commands | A command built from a variable or command substitution is rejected outright |
No rm or sudo | Both are rejected inside sh -c; the sudo error points to the argv form ['sudo', '<binary>', ...] |
| Denied binaries | Anything on denied_command is rejected, inside sh -c as well |
| Shell builtins | A fixed set (cd, echo, test, export, ...) passes without an allowlist entry |
Nested sh -c | Recursively validated with the same rules; a nested script that is not a static string is rejected |
Timeouts
Every tool carries its own timeout, defaulting to 15 minutes (
DEFAULT_TOOL_TIMEOUT; one minute before v1.1.2) and overridden per tool at registration (open_file 10 s, html_template 30 s, fetch_page and search_web 90 s, generate_audio 5 min, download_file 10 min, generate_image 15 min, run_command 30 min; ask_user has none). A background run is limited by its own argument instead: timeout_min up to 60 min (default 60) on background_command and background_subagents; run_websocket and its 24 h timeout were dropped (Removed in v1.3.2). Subagent invocations, including slot-wait time, are capped by MAX_SUBAGENT_TIMEOUT_MIN (30 min).These are package-level values — the environment-variable overrides that used to control them were removed.