Documentation v1.0.9

Sandbox

Every run_command, script tool, and test_tool execution is wrapped by go-pkg/sandbox:

Platform Mechanism
Linux bubblewrap (bwrap)
macOS sandbox-exec

Sandbox restrictions: no privileged execution, restricted filesystem write scope, configurable network access, configurable CPU/memory limits.

Three callers, one entry point

The sandbox has exactly three callers, all calling sandbox.Wrap(ctx, binary, args, workDir, opt) directly:

  1. run_command — arbitrary user-issued commands (internal/tools/runCommand.go)
  2. toolAdapter/script execute — script-tool extensions (script_*, ext_*)
  3. test_tool — trial runs of a script tool's script.py (60 s limit)

There is no wrapper layer between callers and sandbox.Wrap. Adding behavior (e.g., new resource limits) means contributing to go-pkg/sandbox, not adding shims in agenvoy.

Policy injection

filesystem.LoadRuntime() reads ~/.config/agenvoy/config.json at startup and hands the merged policy to go-pkg/sandbox and go-pkg/filesystem. Some policies ship embedded in the binary and merge with the user's entries; others exist only as user config.

Policy Embedded default config.json key Purpose
Sensitive paths configs/jsons/sensitive_path.json sensitive_path Paths holding credentials or key material — reachable only after a password-backed per-session grant. Four buckets: dirs, files, prefixes, extensions
Exclusions configs/jsons/exclude_list.json Paths excluded from listing / walking / searching
Read-only commands configs/jsons/read_only_command.json read_only_command Commands that skip the confirm gate
Denied commands denied_command Binaries run_command refuses outright, inside sh -c too
Denied paths denied_path Paths permanently off limits for reads and writes; no prompt can approve them. Entries must be absolute or ~/-prefixed, the root is refused, and symlink targets are denied as well
SSRF exemptions net_white_list Hosts exempt from the http_request SSRF guard

The command policy is a denylist. There is no binary allowlist: white_list and path_white_list were removed, and sensitive_map was renamed to sensitive_path; a config still carrying any of these keys gets a startup warning rather than silent behavior. Path access outside $HOME is granted per session through the confirmation prompt, not through a config list.

denied_path is handed to go-pkg/sandbox as its denied map; boundary.Resolve and boundary.WriteBinds check it on the agenvoy side before any file tool or write_paths bind proceeds.

Filesystem write guard

go-pkg/filesystem write APIs (WriteFile, WriteJSON, AppendText, CheckDir) all enforce IsDenied internally. 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 needs 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.

Subprocess argv-only schema

run_command accepts only argv: string[] (minItems 1) plus optional write_paths. 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, sudo is rejected (declare write_paths instead), rm is routed to the trash, and cd switches the work directory after verifying the path.

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
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 one minute and overridden per tool at registration (open_file 10 s, fetch_page and search_web 90 s, generate_audio 5 min, download_file 10 min, generate_image 15 min, run_command 60 min). Subagent invocations, including slot-wait time, are capped by MaxSubagentTimeoutMin (30 min).

These are package-level values — the environment-variable overrides that used to control them were removed.

中文