Files
Cyrene_v2/internal/runtime/exec/provider.go
T
AskaEth 09ee561aa7 refactor(exec,io): 文件沙箱单入口收进 io 工具、删 exec.FileSandbox(裁决 13.4.4)+ 修一条假阳性安全断言
- 删除未接线的 exec.FileSandbox(含 5 个用例):文件沙箱从此只有 plugins/tools/io/file.go 一处实现
- io 侧取更严一侧:NUL 拒绝(原 io 缺)、写前按最深存在祖先判定落点(避免越界建目录)、roots 归化到真实路径(修 roots 自身是 symlink 时的全误拒)
- exec/sandbox.go 保留并注释说明:那是**终端工作目录守卫**的原语(terminal.go 在用),不是文件沙箱
- 修假阳性:原 NUL 用例断言错误文本含 "NUL",而 t.TempDir() 目录名自带测试函数名 "…RejectsNULBytePath…"、错误又内嵌整条临时路径 ⇒ 删掉安全检查用例照样绿。改为哨兵错误 ErrNullByte + errors.Is(错误文本逐字节不变),修复后同一变异必红
- 同类排查:file_test.go 三处文本断言判定无风险(含空格的串不可能出现在临时目录名里),保留
2026-10-04 12:29:39 +08:00

132 lines
6.2 KiB
Go

// Package exec implements the unified local execution abstraction for the
// Cyrene agent runtime (DevDocs/design/04-运行时.md §8 and design/02-会话与上下文模型.md §4).
//
// A Provider is the grant-based, sandboxed entry point through which the agent
// may run code on its own host. This milestone ships one concrete piece:
//
// - [TerminalExecutor], the default command provider, which runs allowlisted
// commands inside a locked-down terminal sandbox (command allowlist, working
// directory confinement, environment allowlist, timeout, output truncation).
//
// 文件读写的路径沙箱不在本包:按裁决 13.4.4(design/04 §4),文件访问的 roots
// 收敛、symlink 逃逸与 NUL 拒绝统一由 plugins/tools/io 文件工具承载;本包内
// sandbox.go 的收敛原语只服务于终端沙箱的工作目录 / roots 守卫。
//
// GUI operations and a real virtual machine are reserved for later milestones;
// they surface only through the [Provider] interface and are not implemented
// here. The sandbox is DISABLED BY DEFAULT: an [TerminalExecutor] created from
// a closed config refuses every request with [ErrExecutionDisabled], so the
// agent gains the ability to run code only after the owner explicitly enables
// it (see config.ExecSandbox).
package exec
import (
"context"
"errors"
"time"
)
// DefaultEnabled is the sandbox red line: execution is off unless the owner
// explicitly enables it via config.ExecSandbox.Enabled.
const DefaultEnabled = false
// DefaultTimeout is the per-request deadline used when ExecRequest.Timeout is
// zero. 30s balances responsiveness with enough room for a real command.
const DefaultTimeout = 30 * time.Second
// DefaultMaxOutputBytes is the combined stdout+stderr cap used when
// ExecRequest.MaxOutputBytes is zero (1 MiB).
const DefaultMaxOutputBytes = 1 << 20
// Sentinel errors returned by this package. Use errors.Is to test for them;
// they are always wrapped with %w so the underlying cause stays available.
var (
// ErrExecutionDisabled is returned when the sandbox is closed (config
// Enabled is false). This is the security red line: default off.
ErrExecutionDisabled = errors.New("exec: sandbox disabled for this role")
// ErrEmptyCommand is returned when ExecRequest.Command is empty.
ErrEmptyCommand = errors.New("exec: command is empty")
// ErrCommandNotAllowed is returned when argv[0] is not in the allowlist.
ErrCommandNotAllowed = errors.New("exec: command not in allowlist")
// ErrCommandInjection is returned when a command argument contains shell
// metacharacters, control characters, or a path-traversal component.
ErrCommandInjection = errors.New("exec: command argument rejected")
// ErrWorkDirOutsideRoot is returned when the requested working directory
// escapes the allowed roots.
ErrWorkDirOutsideRoot = errors.New("exec: working directory outside allowed roots")
// ErrEnvNotAllowed is returned when ExecRequest.Env names a variable outside
// the configured environment allowlist.
ErrEnvNotAllowed = errors.New("exec: environment variable not in allowlist")
// ErrTimeout is returned when the command exceeds its deadline and is killed.
ErrTimeout = errors.New("exec: execution timed out")
// ErrExecFailed is returned when a command could not be started or finished.
ErrExecFailed = errors.New("exec: process failed")
// ErrNoAllowedRoot is returned when an operation needs a root but none is
// configured.
ErrNoAllowedRoot = errors.New("exec: no allowed root configured")
// ErrEmptyPath is returned when a path to resolve is empty.
ErrEmptyPath = errors.New("exec: path is empty")
// ErrPathOutsideRoot is returned when a path escapes the allowed roots.
ErrPathOutsideRoot = errors.New("exec: path outside allowed roots")
// ErrSymlinkEscape is returned when a path resolves through a symlink that
// points outside the allowed roots.
ErrSymlinkEscape = errors.New("exec: path escapes root via symlink")
// ErrNullByte is returned when a path contains a NUL byte.
ErrNullByte = errors.New("exec: path contains NUL byte")
// ErrNilLogger is returned by New when no logger is supplied.
ErrNilLogger = errors.New("exec: nil logger")
)
// Provider is the unified local execution abstraction. It covers command
// execution now and, in later milestones, GUI operations and virtual machines,
// all behind the same grant-based, sandboxed interface.
//
// Implementations must honor the context, which carries cancellation and
// deadlines, and must bound their output to ExecRequest.MaxOutputBytes,
// reporting truncation via ExecResult.Truncated.
type Provider interface {
// Execute runs req and returns its outcome, or an error that is one of the
// sentinels above (or wraps one). A non-zero exit code is NOT an error: it
// is reported in ExecResult.ExitCode.
Execute(ctx context.Context, req ExecRequest) (*ExecResult, error)
}
// ExecRequest describes a single local execution.
type ExecRequest struct {
// Command is the argv vector. It must be non-empty; argv[0] is the command
// name that must appear in the allowlist.
Command []string
// WorkDir is the process working directory. It must resolve inside an
// allowed root. When empty, the provider defaults to the first allowed root.
WorkDir string
// Env carries extra environment to set on the process. Only names in the
// environment allowlist are accepted.
Env map[string]string
// Timeout bounds the run. Zero uses DefaultTimeout.
Timeout time.Duration
// Stdin is the optional stdin payload.
Stdin []byte
// MaxOutputBytes caps combined stdout+stderr. Zero uses
// DefaultMaxOutputBytes. Output past the cap is dropped and Truncated is set.
MaxOutputBytes int
}
// ExecResult is the outcome of an execution.
type ExecResult struct {
// Stdout is the captured standard output (truncated to MaxOutputBytes).
Stdout string
// Stderr is the captured standard error (truncated to MaxOutputBytes).
Stderr string
// ExitCode is the process exit code. -1 when terminated by a signal.
ExitCode int
// Truncated reports whether output was dropped because MaxOutputBytes was
// reached.
Truncated bool
// StartedAt is when the process started.
StartedAt time.Time
// EndedAt is when the process finished (or was killed).
EndedAt time.Time
// PID is the process identifier assigned by the OS.
PID int
}