Files
Cyrene_v2/internal/runtime/exec/provider.go
T

130 lines
6.1 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 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
}