sandbox

package
v0.67.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: AGPL-3.0 Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EnvHome            = "HOME"
	EnvStellaAssetsDir = "STELLA_ASSETS_DIR"
	EnvTempDir         = "TMPDIR"
	EnvXDGConfigHome   = "XDG_CONFIG_HOME"
	EnvXDGDataHome     = "XDG_DATA_HOME"
	EnvXDGStateHome    = "XDG_STATE_HOME"
	EnvXDGCacheHome    = "XDG_CACHE_HOME"
	EnvXDGRuntimeDir   = "XDG_RUNTIME_DIR"
)

Agent-facing filesystem environment variable names. Backends render their own path view for these variables, while their ownership stays invariant.

View Source
const (
	// MountWorkspace is the agent's per-agent workspace root and HOME/cwd.
	MountWorkspace = "/workspace"
	// MountUserData is the shared user-data root (toolchain, caches, skills, uploads).
	MountUserData = "/user"
	// MountStellaHome is the read-only system install tree (STELLA_HOME).
	MountStellaHome = "/opt/stella"
	// MountBuiltinSkills is the immutable release bundle view. Isolating
	// backends expose the image/verified-revision projection here, never the
	// retired $STELLA_HOME/.agents/skills mirror.
	MountBuiltinSkills = "/opt/stella/skills/builtin"
)

Mount points an isolating backend exposes inside the sandbox. These are the stable, model-visible paths for the two-root layout (plus the read-only system install tree). The bwrap backend binds the host roots here; higher layers (e.g. the skills tool's skill_dir remap) address skills by these paths instead of host paths. Backends that don't isolate the filesystem (none) ignore them and use host paths directly.

View Source
const EnvRunnerPath = "STELLA_RUNNER_PATH"

EnvRunnerPath snapshots the backend-rendered PATH so login profiles cannot discard or reorder the runner's executable search contract. Runtime owns the value; user configuration must not supply it.

View Source
const (
	// MaxExecOutputBytes caps each captured stdout/stderr stream from untrusted execs.
	MaxExecOutputBytes = 10 << 20 // 10 MiB

)

Variables

View Source
var ErrProjectionConflict = errors.New("sandbox: projection conflicts with exact files")

ErrProjectionConflict reports that an exact projection path already exists with a different tree, mode, or content.

Functions

func ApplyFilesystemEnv added in v0.61.0

func ApplyFilesystemEnv(env map[string]string, view FilesystemView) error

ApplyFilesystemEnv applies the agent-facing filesystem contract to env. It preserves unrelated variables, clears unavailable optional roots and XDG_RUNTIME_DIR, and places persistent XDG directories under UserDir or Home.

func EnsureMiseShims added in v0.49.1

func EnsureMiseShims(stellaHome, userToolsDir string) error

EnsureMiseShims prepares the mise shims a sandbox session resolves against, before the session's PATH is used. It relinks the shared system-tree shims (always) and, when userToolsDir is set, seeds and relinks the per-user tree via EnsureUserMiseHome. Splitting the two trees keeps each concern separate while a single call site guarantees both resolve under the active backend's remap. All backends call this, including docker: the relinked relative shims and seeded per-user tree are mounted into the container and resolve against the image's in-image system tree (#436).

func EnsurePrivateDir added in v0.38.0

func EnsurePrivateDir(path string) error

EnsurePrivateDir creates path if needed and enforces owner-only permissions. MkdirAll does not chmod existing directories, so callers must use this for security-sensitive shared host directories.

func EnsureUserMiseHome added in v0.48.0

func EnsureUserMiseHome(stellaHome, userToolsDir string) error

EnsureUserMiseHome creates the per-user mise tree at userToolsDir and seeds it with the shared system installs. Pointing a session's MISE_DATA_DIR at this tree gives the agent a real, writable mise home — it resolves the builtin tools (via the seeded symlinks) and can install its own tools/versions alongside, layered above the read-only system installs. Idempotent and safe to call before every session; a no-op when userToolsDir is empty.

The tree is created 0700 (like the sibling per-user temp dir) so other host users can't read one tenant's installed tools, and every structural directory is verified to be a real directory — never a symlink. The tree is mounted writable into the sandbox and persists across sessions, so a prior session's agent could replace a structural dir with a symlink; since this runs unsandboxed with server privileges, following such a link would let seeding escape the tree.

The "shims" dir is seeded empty on purpose: builtin tools resolve through the system shims (kept on PATH behind the per-user shims by HostEnvBuildPath), and the per-user shims fill in only as the agent installs its own tools. Any shims the agent did create are relinked to a relative target so a persisted tree stays usable if the same user later runs under a different backend.

func ExpandPathVariables added in v0.61.0

func ExpandPathVariables(path string, env map[string]string) (string, error)

ExpandPathVariables expands one allowlisted variable only when it appears at the beginning of path. Values come exclusively from env, never the host process environment. Other paths are returned unchanged.

func HostEnvBuildPath added in v0.33.0

func HostEnvBuildPath(stellaHome, userShimsDir string) string

HostEnvBuildPath returns a sanitized PATH suitable for host-execution sandbox backends (local, none). It prepends the per-user mise shims (so a user's own tool versions win), then Stella's embedded runtimes and the system mise shims, and filters host PATH entries to a safe allowlist on Linux. An empty userShimsDir is dropped, so callers without a per-user tree pass "".

func HostEnvCopy added in v0.33.0

func HostEnvCopy(env map[string]string)

HostEnvCopy copies a fixed allowlist of host environment variables into env. Only locale, terminal, and proxy variables are included.

func HostEnvPathAllowed added in v0.33.0

func HostEnvPathAllowed(entry, stellaBin string) bool

HostEnvPathAllowed reports whether a PATH entry is in the safe allowlist.

func IsPolicyCompatibilityError

func IsPolicyCompatibilityError(err error) bool

IsPolicyCompatibilityError reports whether an error is a policy compatibility error.

func LogSessionClosed

func LogSessionClosed(sessionID, backend, reason string)

func LogSessionCreated

func LogSessionCreated(sessionID, backend string, policy Policy)

func LogUnsupportedBackend

func LogUnsupportedBackend(policy Policy, attempted []string, reason string)

func MiseShimsDir added in v0.38.0

func MiseShimsDir(stellaHome string) string

MiseShimsDir returns the mise shims directory for host-execution sandbox backends. Tools installed by the manifest/org reconcilers are exposed here as shims (not copied into bin), so it must be on PATH for them to resolve.

func MiseToolsDir added in v0.38.0

func MiseToolsDir(stellaHome string) string

MiseToolsDir returns the root MISE_DATA_DIR for Stella-managed mise installs. This is the single source of truth for the on-disk layout: the manifest/org reconcilers install into it and the sandbox PATH is built from it, so both sides must derive their paths from here to stay in lockstep.

func MiseUserShimsDir added in v0.48.0

func MiseUserShimsDir(userToolsDir string) string

MiseUserShimsDir returns the per-user mise shims directory. It is prepended to PATH ahead of the system shims so a user's own tool versions win.

func MiseUserToolsDir added in v0.48.0

func MiseUserToolsDir(stellaHome, principalDir, id string) string

MiseUserToolsDir returns the per-principal writable MISE_DATA_DIR. It mirrors a real machine's per-user mise home: each principal gets one tree shared by all their agents, layered above the shared read-only system installs. principalDir is the home subtree — always "users", the only top-level isolation boundary (#442) — and id is the principal's key: a raw user ID, or a channel group's ID under a "group-" prefix so equal raw IDs across users and groups can never collide. Keying as {stellaHome}/{principalDir}/{id}/.mise-tools never depends on the agent, so all of a principal's agents share one tree. An empty or unsafe argument yields "" so callers fall back to the system tree.

func NewSessionID

func NewSessionID() string

NewSessionID returns a cryptographically random session identifier. Using random IDs avoids Docker container name collisions across test runs.

func POSIXPathRelative added in v0.61.0

func POSIXPathRelative(root, target string) (string, bool)

POSIXPathRelative returns target relative to root when target is contained in root after POSIX normalization. It accepts Windows-style separators at this Linux-container boundary and returns "." for root itself.

func PerUserMiseDataDir added in v0.48.0

func PerUserMiseDataDir(env map[string]string, stellaHome string) string

PerUserMiseDataDir returns the per-user MISE_DATA_DIR carried in a session's env, or "" when it points at the shared read-only system tree (or is unset). It lets a host backend recover the per-user mise home — to derive the shims dir for PATH — from the env it already remaps, so the FilesystemPolicy needs no mise-specific field. stellaHome is the host STELLA_HOME used to recognize the system tree; the returned path is whatever scope the env holds (host path here, the backend remaps it as needed).

Precondition: env's MISE_DATA_DIR and stellaHome must be in the same scope (both host paths, or both sandbox paths). Callers that remap MISE_* env vars must read this before remapping (see adjustPolicy), since after remap the data dir no longer matches the host system tree this compares against.

func RelinkSystemMiseShims added in v0.49.1

func RelinkSystemMiseShims(stellaHome string) error

RelinkSystemMiseShims rewrites the shared system-tree shims to relative targets. Idempotent and safe to call before every session; see relinkShimsToLocalMise.

func ShellEnvPath added in v0.64.0

func ShellEnvPath(stellaHome string) string

ShellEnvPath returns the read-only startup file that restores Stella-managed mise paths after a nested login shell replaces PATH. The file is installed by resources/binaries.EnsureTools alongside the embedded mise binary.

func SplitLeadingPathVariable added in v0.61.0

func SplitLeadingPathVariable(path string) (name, suffix string, hasVariable bool, err error)

SplitLeadingPathVariable parses the restricted path-variable grammar shared by agent-facing file paths. It accepts exactly one leading $NAME or ${NAME}, optionally followed by a path separator and suffix; it does not expand or allowlist the variable.

func StellaHomeSandboxDirs added in v0.38.1

func StellaHomeSandboxDirs() []string

StellaHomeSandboxDirs returns the subdirectory names (relative to STELLA_HOME) that sandbox backends must expose. Every backend — bwrap, Docker, none — should derive its mount list from this slice so adding a new directory is a one-line change. The returned names use filepath.Separator and are safe to join with any absolute root.

Types

type CappedOutputBuffer added in v0.60.0

type CappedOutputBuffer struct {
	// contains filtered or unexported fields
}

CappedOutputBuffer stores command output up to a byte ceiling and discards the rest.

func NewCappedOutputBuffer added in v0.60.0

func NewCappedOutputBuffer(limit int) *CappedOutputBuffer

NewCappedOutputBuffer returns an io.Writer that captures at most limit bytes.

func NewExecOutputBuffer added in v0.60.0

func NewExecOutputBuffer() *CappedOutputBuffer

NewExecOutputBuffer returns a capped buffer for one exec output stream.

func (*CappedOutputBuffer) Bytes added in v0.60.0

func (b *CappedOutputBuffer) Bytes() []byte

func (*CappedOutputBuffer) String added in v0.60.0

func (b *CappedOutputBuffer) String() string

func (*CappedOutputBuffer) Truncated added in v0.60.0

func (b *CappedOutputBuffer) Truncated() bool

func (*CappedOutputBuffer) Write added in v0.60.0

func (b *CappedOutputBuffer) Write(p []byte) (int, error)

type DirEntry

type DirEntry struct {
	Name  string
	IsDir bool
	Size  int64
}

DirEntry is the provider-neutral directory metadata used by prompt and tool callers without exposing an os.DirEntry backed by a physical provider path.

type EnvRefresher added in v0.49.0

type EnvRefresher interface {
	RefreshEnv(updates map[string]string)
}

EnvRefresher is implemented by sessions whose injected environment can be updated after creation, so a caller can rotate a credential (e.g. an expiring OAuth access token) without tearing down and recreating the session. Updates are applied atomically and are visible to every subsequent Exec/StartProcess. Sessions that don't run real processes (e.g. the no-op session) may omit it.

type ExecOptions

type ExecOptions struct {
	Cwd     string
	Env     map[string]string
	Timeout time.Duration
}

type ExecResult

type ExecResult struct {
	Stdout   string
	Stderr   string
	ExitCode int
	// TimedOut distinguishes a sandbox-enforced deadline from another kill
	// that also uses a negative exit-code sentinel.
	TimedOut bool
}

type Factory

type Factory interface {
	CreateSession(ctx context.Context, policy Policy) (Session, error)
	Supported(policy Policy) error
	Name() string
	Available() bool
}

Factory creates sessions from policies.

type FileAccess added in v0.64.0

type FileAccess interface {
	ReadFile(path string) ([]byte, error)
	ReadDir(path string) ([]DirEntry, error)
	Stat(path string) (FileInfo, error)
	WriteFile(path string, content []byte, mode fs.FileMode) error
	ProjectFiles(path string, files []ProjectedFile) error
	// ProjectTempFiles publishes an exact tree beneath the active Session's
	// process-visible temporary root and returns that visible absolute path.
	// Choosing the generation, temporary root, and FileAccess capability is one
	// operation so resilient recreation cannot combine coordinates from one
	// generation with files from another.
	ProjectTempFiles(path string, files []ProjectedFile) (string, error)
}

FileAccess is the narrow data-filesystem capability used by prompt construction, core tools, and exact per-Session projections. Paths use the same coordinates as WorkingDir and Policy.Env. Provider runtime/image paths may be executable by a process without belonging to this authorized data view; cross-mount symlinks fail closed rather than changing capabilities.

type FileInfo added in v0.64.0

type FileInfo struct {
	IsDir bool
	Size  int64
}

FileInfo is the metadata core tools need without exposing an os.FileInfo backed by a provider path.

type FileTooLargeError added in v0.65.0

type FileTooLargeError struct {
	Size  int64
	Limit int64
}

FileTooLargeError reports that a backend refused to transfer a complete file. It deliberately carries no path so provider-internal filesystem coordinates cannot cross the sandbox boundary; the caller already has the public path.

func (*FileTooLargeError) Error added in v0.65.0

func (e *FileTooLargeError) Error() string

type FileView added in v0.64.0

type FileView struct {
	Policy     Policy
	WorkingDir string
	Files      FileAccess
}

FileView binds process-visible metadata and filesystem access to one selected Session generation. It does not keep that generation alive; if the backend dies during an operation, callers get an error instead of silently continuing the same composed operation against a replacement generation.

func SelectFileView added in v0.64.0

func SelectFileView(ctx context.Context, session Session) (FileView, error)

SelectFileView chooses one operational Session generation. Resilient sessions recreate a dead backend before taking the snapshot; raw sessions expose their current immutable metadata and filesystem capability directly.

type FilesystemPolicy

type FilesystemPolicy struct {
	// WorkingDir is the process-visible POSIX working directory.
	WorkingDir string

	// Mounts contains the authorized data roots in the active process view.
	Mounts []Mount
}

FilesystemPolicy defines filesystem constraints for a sandbox session.

type FilesystemView added in v0.61.0

type FilesystemView struct {
	Home          string
	SharedDataDir string
	TempDir       string
}

FilesystemView is the agent-visible filesystem path view a backend exposes. Its roots are canonical backend views: POSIX sandbox paths or native host paths. SharedDataDir is optional; when present, STELLA_ASSETS_DIR is derived beneath it and persistent XDG directories live there. Without SharedDataDir, persistent XDG directories live under Home. Home and TempDir are required.

type Mount added in v0.60.0

type Mount struct {
	SandboxPath string
	Access      MountAccess
}

Mount declares one process-visible data root authorized through Session.Files. Provider-private physical backing paths deliberately do not belong to Policy.

type MountAccess added in v0.60.0

type MountAccess uint8

MountAccess is the access mode a sandbox mount grants inside the sandbox.

const (
	// MountReadOnly allows reads but rejects writes through host-side tools and
	// asks isolating backends for a read-only bind when the platform supports it.
	MountReadOnly MountAccess = iota
	// MountReadWrite allows reads and writes.
	MountReadWrite
)

type NetworkMode

type NetworkMode string

NetworkMode defines the network access mode for a sandbox session.

const (
	// NetworkDisabled blocks all network access.
	NetworkDisabled NetworkMode = "disabled"
	// NetworkAllowAll allows unrestricted network access.
	NetworkAllowAll NetworkMode = "allow_all"
)

type NetworkPolicy

type NetworkPolicy struct {
	// Mode is the network access mode: disabled | allow_all.
	Mode NetworkMode

	// Timeout for network operations. Zero means no timeout.
	Timeout time.Duration
}

NetworkPolicy defines network constraints for a sandbox session.

type Policy

type Policy struct {
	// Filesystem policy
	Filesystem FilesystemPolicy

	// Network policy
	Network NetworkPolicy

	// Env holds environment variables injected into sandboxed processes.
	Env map[string]string

	// InheritEnv includes system environment variables when true.
	InheritEnv bool

	// Timeout for process execution. Zero means no timeout.
	Timeout time.Duration
}

Policy is an immutable, backend-agnostic session policy describing requested limits for filesystem, network, and process constraints.

func (Policy) NetworkModeOrDefault

func (p Policy) NetworkModeOrDefault() NetworkMode

NetworkModeOrDefault returns the network mode with default applied.

func (Policy) Validate

func (p Policy) Validate() error

Validate returns an error if the policy contains invalid configurations. This validates policy structure, not backend compatibility.

type PolicyCompatibilityError

type PolicyCompatibilityError struct {
	Backend string
	Policy  Policy
	Reason  string
}

PolicyCompatibilityError indicates a policy is not compatible with a backend.

func (*PolicyCompatibilityError) Error

func (e *PolicyCompatibilityError) Error() string

type ProcessHandle

type ProcessHandle interface {
	PID() int
	Wait(ctx context.Context) (ExecResult, error)
	Stdin() io.WriteCloser
	Stdout() io.ReadCloser
	Stderr() io.ReadCloser
	Close() error
}

type ProcessRequest

type ProcessRequest struct {
	Path    string
	Args    []string
	Cwd     string
	Env     map[string]string
	Timeout time.Duration
}

type ProjectedFile added in v0.64.0

type ProjectedFile struct {
	Path    string
	Content []byte
	Mode    fs.FileMode
}

ProjectedFile is one file in an exact-at-publication, no-replace, disposable Session projection.

type ResilientSession added in v0.38.0

type ResilientSession struct {
	// contains filtered or unexported fields
}

ResilientSession wraps a Session and transparently recreates it when the underlying session has been closed unexpectedly (e.g. container exited or sandbox process crashed). Explicit Close calls are permanent — no recreation after that. Policy and WorkingDir are cheap snapshots of the retained generation and never create a container; operational methods recreate with their caller context. Selection is atomic per method call, not across a caller's separate metadata and Files operations. Callers that compose metadata with one or more file operations use SelectFileView to bind them to one generation; a backend failure then fails that view instead of switching it.

func NewResilientSession added in v0.38.0

func NewResilientSession(session Session, create SessionCreator) *ResilientSession

NewResilientSession wraps an existing session with auto-recreation support.

func (*ResilientSession) Alive added in v0.38.0

func (r *ResilientSession) Alive() bool

func (*ResilientSession) Close added in v0.38.0

func (r *ResilientSession) Close() error

func (*ResilientSession) Done added in v0.38.0

func (r *ResilientSession) Done() <-chan struct{}

func (*ResilientSession) Exec added in v0.38.0

func (r *ResilientSession) Exec(ctx context.Context, command string, opts ExecOptions) (ExecResult, error)

func (*ResilientSession) Files added in v0.64.0

func (r *ResilientSession) Files() FileAccess

func (*ResilientSession) Policy added in v0.38.0

func (r *ResilientSession) Policy() Policy

func (*ResilientSession) RefreshEnv added in v0.49.0

func (r *ResilientSession) RefreshEnv(updates map[string]string)

RefreshEnv overlays injected env entries for subsequent Exec and StartProcess calls. Keeping the overlay at this wrapper boundary makes credential rotation backend-independent. A recreated inner session is built from current state and clears the overlay in ensureAlive.

func (*ResilientSession) StartProcess added in v0.38.0

func (r *ResilientSession) StartProcess(ctx context.Context, req ProcessRequest) (ProcessHandle, error)

func (*ResilientSession) WorkingDir added in v0.38.0

func (r *ResilientSession) WorkingDir() string

type Session

type Session interface {
	// Lifecycle
	Policy() Policy
	Close() error
	Alive() bool
	Done() <-chan struct{}

	// Host process surface
	Exec(ctx context.Context, command string, opts ExecOptions) (ExecResult, error)
	StartProcess(ctx context.Context, req ProcessRequest) (ProcessHandle, error)

	// Files returns mediated access to the process-visible filesystem. Physical
	// provider paths never cross this boundary.
	Files() FileAccess
	WorkingDir() string
}

Session is the plugin-facing sandbox surface: lifecycle + mediated host access. It combines what was previously the Session lifecycle interface and the Host file/process interface into a single type so plugins receive one coherent value.

func NopSession

func NopSession() Session

NopSession returns a no-op session for tests that need only lifecycle or process behavior. Its filesystem capability fails closed; filesystem tests must use a real provider session or inject an explicit FileAccess fake.

type SessionCreator added in v0.38.0

type SessionCreator func(ctx context.Context) (Session, error)

SessionCreator creates a new session. Used by ResilientSession to recreate the underlying session when it is found closed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL