sandbox

package
v0.60.1 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Index

Constants

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"
	// MountAgentSkills is the read-only mount of the admin-managed, agent-bound
	// (system_agent scope) skills dir. It lives outside the two roots (its host
	// dir is the user-independent agent definition tree, not the per-agent
	// workspace), so it gets its own fixed mount instead of mapping onto /workspace.
	MountAgentSkills = "/opt/stella/agent-skills"
	// MountSystemDBSkills is the read-only mount of DB-installed system-scope
	// skills. They live in a dedicated dir (a sibling of the shipped built-ins
	// under STELLA_HOME, not mixed into it), so they get their own fixed mount.
	MountSystemDBSkills = "/opt/stella/db-skills"
)

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 (
	// MaxExecOutputBytes caps each captured stdout/stderr stream from untrusted execs.
	MaxExecOutputBytes = 10 << 20 // 10 MiB

)

Variables

This section is empty.

Functions

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 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 the system mise shims and stella bin directories, 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 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 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 retained for use by prompt_host.go which reads directories via os.ReadDir and needs a uniform entry type shared with sandbox callers.

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
}

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 FilesystemPolicy

type FilesystemPolicy struct {
	// WorkspaceRoot is the host workspace root. When empty, WorkingDir is used for
	// backwards compatibility and for relative-path anchoring in non-isolating code.
	WorkspaceRoot string

	// WorkingDir is the requested working directory. Producers may provide either
	// a host path under WorkspaceRoot or the corresponding sandbox path; backends
	// normalize it through Mounts before execution.
	WorkingDir string

	// Mounts is the complete host→sandbox mount plan for isolating backends.
	Mounts []Mount

	// TempDirHost is the host directory mounted as /tmp inside the sandbox.
	// Empty means the backend chooses a session-local temp directory. Non-empty
	// directories are guaranteed to exist with 0700 permissions before the
	// backend sees the policy; they may be shared across sessions.
	TempDirHost string
}

FilesystemPolicy defines filesystem constraints for a sandbox session.

type Host

type Host = Session

Host is an alias for Session kept for internal use by the runner and core tools. New code should use Session directly.

type Mount added in v0.60.0

type Mount struct {
	HostPath    string
	SandboxPath string
	Access      MountAccess
}

Mount maps a host path into the sandbox view.

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 PathResolver added in v0.60.0

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

PathResolver is the shared host-side filesystem boundary for in-process tools. It translates agent-visible paths to host paths only when the result stays inside a declared mount, and it rejects symlink traversal below mount roots.

func NewPathResolver added in v0.60.0

func NewPathResolver(cfg PathResolverConfig) *PathResolver

NewPathResolver returns a resolver for the supplied filesystem view. The workspace mount is added as a compatibility default when Mounts omits it.

func (*PathResolver) Mounts added in v0.60.0

func (r *PathResolver) Mounts() []Mount

Mounts returns a copy of the normalized mount table.

func (*PathResolver) ResolvePath added in v0.60.0

func (r *PathResolver) ResolvePath(agentPath string) (ResolvedPath, error)

ResolvePath resolves an agent path for reading.

func (*PathResolver) ResolveWritePath added in v0.60.0

func (r *PathResolver) ResolveWritePath(agentPath string) (ResolvedPath, error)

ResolveWritePath resolves an agent path for writing and rejects read-only mounts.

func (*PathResolver) ToHostPath added in v0.60.0

func (r *PathResolver) ToHostPath(sandboxPath string) (string, bool)

ToHostPath maps a sandbox-space path to its host path using the deepest mount.

func (*PathResolver) ToSandboxPath added in v0.60.0

func (r *PathResolver) ToSandboxPath(hostPath string) (string, bool)

ToSandboxPath maps a host path to its sandbox-space path using the deepest mount.

type PathResolverConfig added in v0.60.0

type PathResolverConfig struct {
	WorkspaceRoot string
	WorkingDir    string
	Mounts        []Mount
	TempDirHost   string
}

PathResolverConfig describes the mount view used by PathResolver.

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.

func (Policy) WorkspaceRootOrDefault

func (p Policy) WorkspaceRootOrDefault() string

WorkspaceRootOrDefault returns the mounted sandbox root on the host.

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 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.

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) 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) ResolvePath added in v0.38.0

func (r *ResilientSession) ResolvePath(path string) (string, error)

func (*ResilientSession) ResolveWritePath added in v0.38.0

func (r *ResilientSession) ResolveWritePath(path string) (string, error)

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 ResolvedPath added in v0.60.0

type ResolvedPath struct {
	HostPath    string
	SandboxPath string
	Mount       Mount
}

ResolvedPath is a path resolved through a mount.

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)

	// Path resolution — use os.* with the resolved path for file I/O.
	// ResolvePath validates read access; use ResolveWritePath for write operations.
	ResolvePath(path string) (string, error)
	// ResolveWritePath is like ResolvePath but additionally rejects paths in
	// read-only mounts.
	ResolveWritePath(path string) (string, error)
	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 testing.

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