// 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 two concrete pieces: // // - [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). // - [FileSandbox], which confines file read/write/list access to a set of // allowed root directories. // // 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 }