sandbox

package
v0.38.1 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

Package sandbox provides secure code execution in isolated containers.

Index

Constants

View Source
const (
	// LabelManaged identifies containers created by ethpandaops-panda.
	LabelManaged = "io.ethpandaops-panda.managed"
	// LabelCreatedAt stores the Unix timestamp when the container was created.
	LabelCreatedAt = "io.ethpandaops-panda.created-at"
	// LabelSessionID stores the session ID for session containers.
	LabelSessionID = "io.ethpandaops-panda.session-id"
	// LabelOwnerID stores the owner ID (GitHub user ID) if auth is enabled.
	LabelOwnerID = "io.ethpandaops-panda.owner-id"
	// LabelInstance identifies which server instance created this container.
	LabelInstance = "io.ethpandaops-panda.instance"
)

Container label keys for identifying and managing ethpandaops-panda containers.

View Source
const (
	// EnvAPIToken carries the per-execution runtime API token into the sandbox.
	EnvAPIToken = "ETHPANDAOPS_API_TOKEN"
	// EnvAPIURL carries the server API URL the sandbox calls back into.
	EnvAPIURL = "ETHPANDAOPS_API_URL"
	// EnvAPIUDS carries the unix socket the sandbox calls the server on (direct
	// backend, whose netns has no TCP route); unset for docker/gvisor.
	EnvAPIUDS = "ETHPANDAOPS_API_UDS"
	// EnvExecutionID identifies the execution for storage tagging and correlation.
	EnvExecutionID = "ETHPANDAOPS_EXECUTION_ID"
	// EnvSessionID identifies the session so storage groups a multi-turn
	// session's outputs under one directory. Empty for sessionless executions.
	EnvSessionID = "ETHPANDAOPS_SESSION_ID"
)

Environment variable names exchanged between the server and sandboxed code. The set/inject side (execsvc) and the filter/inject side (sandbox backend) must agree on these keys, so they live in one place.

Variables

View Source
var ErrSandboxDisabled = errors.New(
	`sandbox.backend is "none": this panda-server runs without a Python sandbox, so execute_python and sandbox sessions are unavailable`,
)

ErrSandboxDisabled is returned by the "none" backend for any execution or session operation. With sandbox.backend set to "none" the server runs without a container runtime, so execute_python and sandbox sessions are unavailable.

Functions

func CreateMounts

func CreateMounts(sharedDir, outputDir string) []mount.Mount

CreateMounts creates the volume mounts for sandbox execution. sharedDir is mounted read-only at /shared (contains the script). outputDir is mounted read-write at /output (for generated files).

func RunDirectSandboxInitIfRequested added in v0.38.0

func RunDirectSandboxInitIfRequested() bool

RunDirectSandboxInitIfRequested runs the trampoline (and never returns) when re-exec'd as one, else is a no-op. Call before flag/config parsing.

func SandboxEnvDefaults

func SandboxEnvDefaults() map[string]string

SandboxEnvDefaults returns default environment variables for sandbox execution. These ensure proper operation as the "nobody" user with no home directory.

Types

type BackendType

type BackendType string

BackendType represents the available sandbox backend types.

const (
	// BackendDocker uses standard Docker containers.
	BackendDocker BackendType = "docker"
	// BackendGVisor uses Docker with gVisor runtime for enhanced isolation.
	BackendGVisor BackendType = "gvisor"
	// BackendNone disables execution entirely. The server runs without a
	// container runtime and execute_python is unavailable.
	BackendNone BackendType = "none"
	// BackendDirect runs Python code directly as a subprocess on the host —
	// for use inside a Kubernetes pod where the pod boundary is the isolation.
	BackendDirect BackendType = "direct"
)

type DirectBackend added in v0.38.0

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

DirectBackend runs Python as a host subprocess (no container), for pods where the pod boundary is the isolation. Session lifecycle is delegated to SessionManager.

func NewDirectBackend added in v0.38.0

func NewDirectBackend(cfg config.SandboxConfig, log logrus.FieldLogger) (*DirectBackend, error)

NewDirectBackend creates a new direct execution backend.

func (*DirectBackend) CanCreateSession added in v0.38.0

func (b *DirectBackend) CanCreateSession(ctx context.Context, ownerID string) (bool, int, int)

CanCreateSession checks if a new session can be created within limits.

func (*DirectBackend) CreateSession added in v0.38.0

func (b *DirectBackend) CreateSession(ctx context.Context, ownerID string, _ map[string]string) (string, error)

CreateSession creates a new persistent workspace and returns its session ID.

func (*DirectBackend) DestroySession added in v0.38.0

func (b *DirectBackend) DestroySession(ctx context.Context, sessionID, ownerID string) error

DestroySession removes a session's workspace and cleans up.

func (*DirectBackend) Execute added in v0.38.0

Execute runs Python as a subprocess; a non-empty req.SessionID runs it in that session's persistent workspace and carries session info in the result.

func (*DirectBackend) ListSessions added in v0.38.0

func (b *DirectBackend) ListSessions(ctx context.Context, ownerID string) ([]SessionInfo, error)

ListSessions returns all active sessions, optionally filtered by ownerID.

func (*DirectBackend) Name added in v0.38.0

func (b *DirectBackend) Name() string

Name returns the backend name.

func (*DirectBackend) SessionsEnabled added in v0.38.0

func (b *DirectBackend) SessionsEnabled() bool

SessionsEnabled returns whether sessions are enabled in config.

func (*DirectBackend) Start added in v0.38.0

func (b *DirectBackend) Start(ctx context.Context) error

Start validates that a Python interpreter is available on the host and starts the session manager (which runs cleanup when sessions are enabled).

func (*DirectBackend) Stop added in v0.38.0

func (b *DirectBackend) Stop(ctx context.Context) error

Stop shuts down the session manager, which reaps all session workspaces.

type DockerBackend

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

DockerBackend implements sandbox execution using standard Docker containers.

func NewDockerBackend

func NewDockerBackend(cfg config.SandboxConfig, log logrus.FieldLogger) (*DockerBackend, error)

NewDockerBackend creates a new Docker sandbox backend.

func (*DockerBackend) CanCreateSession

func (b *DockerBackend) CanCreateSession(ctx context.Context, ownerID string) (bool, int, int)

CanCreateSession checks if a new session can be created. Returns (canCreate, currentCount, maxAllowed).

func (*DockerBackend) CreateSession

func (b *DockerBackend) CreateSession(ctx context.Context, ownerID string, env map[string]string) (string, error)

CreateSession creates a new empty session and returns its ID.

func (*DockerBackend) DestroySession

func (b *DockerBackend) DestroySession(ctx context.Context, sessionID, ownerID string) error

DestroySession destroys a session by ID. If ownerID is non-empty, verifies ownership before destroying.

func (*DockerBackend) Execute

Execute runs Python code in a Docker container.

func (*DockerBackend) ListSessions

func (b *DockerBackend) ListSessions(ctx context.Context, ownerID string) ([]SessionInfo, error)

ListSessions returns all active sessions. If ownerID is non-empty, filters by owner.

func (*DockerBackend) Name

func (b *DockerBackend) Name() string

Name returns the backend name.

func (*DockerBackend) SessionsEnabled

func (b *DockerBackend) SessionsEnabled() bool

SessionsEnabled returns whether sessions are enabled.

func (*DockerBackend) Start

func (b *DockerBackend) Start(ctx context.Context) error

Start initializes the Docker client and verifies connectivity.

func (*DockerBackend) Stop

func (b *DockerBackend) Stop(ctx context.Context) error

Stop cleans up any active containers and closes the Docker client.

type ExecuteRequest

type ExecuteRequest struct {
	// Code is the Python code to execute.
	Code string
	// ExecutionID identifies this execution. When empty, the backend mints one.
	// Supplying it lets the caller correlate runtime tokens, storage tagging, and logs.
	ExecutionID string
	// Env contains additional environment variables to set in the sandbox.
	Env map[string]string
	// Timeout overrides the default execution timeout. If zero, uses the config default.
	Timeout time.Duration
	// SessionID is an optional session ID to reuse a persistent container.
	// If empty, a new session is created. If provided, the existing session is reused.
	SessionID string
	// OwnerID is the GitHub user ID that owns the session.
	// Required for session creation and verification.
	OwnerID string
}

ExecuteRequest contains the parameters for code execution.

type ExecutionResult

type ExecutionResult struct {
	// Stdout contains the standard output from execution.
	Stdout string
	// Stderr contains the standard error from execution.
	Stderr string
	// ExitCode is the process exit code (0 = success).
	ExitCode int
	// ExecutionID is a unique identifier for this execution.
	ExecutionID string
	// OutputFiles lists file names created in /output directory.
	OutputFiles []string
	// Metrics contains any metrics reported by the executed code.
	Metrics map[string]any
	// DurationSeconds is the wall-clock execution time.
	DurationSeconds float64

	// Session-related fields (only populated when sessions are enabled).
	// SessionID is the session identifier. Can be used to reuse this session.
	SessionID string
	// SessionFiles lists files persisted in the session's /workspace directory.
	SessionFiles []SessionFile
	// SessionTTLRemaining is the time until this session expires from inactivity.
	SessionTTLRemaining time.Duration
}

ExecutionResult contains the output from code execution.

type GVisorBackend

type GVisorBackend struct {
	*DockerBackend
}

GVisorBackend implements sandbox execution using Docker with gVisor runtime. gVisor provides user-space kernel isolation, making container escapes significantly harder compared to standard Docker. Only available on Linux.

func NewGVisorBackend

func NewGVisorBackend(cfg config.SandboxConfig, log logrus.FieldLogger) (*GVisorBackend, error)

NewGVisorBackend creates a new gVisor sandbox backend.

func (*GVisorBackend) Name

func (b *GVisorBackend) Name() string

Name returns the backend name.

type NoneBackend added in v0.38.0

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

NoneBackend is a no-op sandbox backend for deployments that run panda-server without a container runtime (e.g. servers that only use the structured operations and never call execute_python). Start and Stop succeed as no-ops; every execution and session method returns ErrSandboxDisabled.

func NewNoneBackend added in v0.38.0

func NewNoneBackend(log logrus.FieldLogger) *NoneBackend

NewNoneBackend creates a sandbox backend that performs no execution.

func (*NoneBackend) CanCreateSession added in v0.38.0

func (b *NoneBackend) CanCreateSession(context.Context, string) (bool, int, int)

CanCreateSession reports that no session can be created, mirroring the Docker backend's response when sessions are disabled.

func (*NoneBackend) CreateSession added in v0.38.0

func (b *NoneBackend) CreateSession(context.Context, string, map[string]string) (string, error)

CreateSession always fails: this backend has no sessions.

func (*NoneBackend) DestroySession added in v0.38.0

func (b *NoneBackend) DestroySession(context.Context, string, string) error

DestroySession always fails: this backend has no sessions.

func (*NoneBackend) Execute added in v0.38.0

Execute always fails: this backend cannot run Python code.

func (*NoneBackend) ListSessions added in v0.38.0

func (b *NoneBackend) ListSessions(context.Context, string) ([]SessionInfo, error)

ListSessions always fails: this backend has no sessions.

func (*NoneBackend) Name added in v0.38.0

func (b *NoneBackend) Name() string

Name returns the backend name.

func (*NoneBackend) SessionsEnabled added in v0.38.0

func (b *NoneBackend) SessionsEnabled() bool

SessionsEnabled reports that sessions are unavailable.

func (*NoneBackend) Start added in v0.38.0

func (b *NoneBackend) Start(context.Context) error

Start is a no-op; there is no container runtime to connect to.

func (*NoneBackend) Stop added in v0.38.0

func (b *NoneBackend) Stop(context.Context) error

Stop is a no-op; the backend holds no resources.

type SecurityConfig

type SecurityConfig struct {
	// User to run the container as (e.g., "nobody").
	User string
	// ReadonlyRootfs makes the root filesystem read-only.
	ReadonlyRootfs bool
	// DropCapabilities lists Linux capabilities to drop.
	DropCapabilities []string
	// SecurityOpts are additional security options (e.g., "no-new-privileges:true").
	SecurityOpts []string
	// PidsLimit restricts the number of processes in the container.
	PidsLimit int64
	// MemoryLimit in bytes.
	MemoryLimit int64
	// CPUQuota as a percentage of one CPU (100000 = 1 CPU).
	CPUQuota int64
	// CPUPeriod is the CPU CFS scheduler period.
	CPUPeriod int64
	// TmpfsSize is the size of the /tmp tmpfs mount.
	TmpfsSize string
	// Runtime specifies the container runtime (e.g., "" for default, "runsc" for gVisor).
	Runtime string
}

SecurityConfig holds security settings for container execution.

func DefaultSecurityConfig

func DefaultSecurityConfig(memoryLimit string, cpuLimit float64) (*SecurityConfig, error)

DefaultSecurityConfig returns the default security configuration for sandboxed execution. Returns an error if the memory limit cannot be parsed.

func GVisorSecurityConfig

func GVisorSecurityConfig(memoryLimit string, cpuLimit float64) (*SecurityConfig, error)

GVisorSecurityConfig returns security configuration for gVisor-based execution. Returns an error if the memory limit cannot be parsed.

func (*SecurityConfig) ApplyToHostConfig

func (s *SecurityConfig) ApplyToHostConfig(hostConfig *container.HostConfig)

ApplyToHostConfig applies security settings to a Docker HostConfig.

type SecurityConfigFunc

type SecurityConfigFunc func(memoryLimit string, cpuLimit float64) (*SecurityConfig, error)

SecurityConfigFunc is a function that returns security configuration.

type Service

type Service interface {
	// Start initializes the sandbox backend (e.g., connecting to Docker).
	Start(ctx context.Context) error
	// Stop cleans up resources and any active containers.
	Stop(ctx context.Context) error
	// Execute runs Python code in an isolated container.
	Execute(ctx context.Context, req ExecuteRequest) (*ExecutionResult, error)
	// Name returns the backend name for logging/metrics.
	Name() string

	// ListSessions returns all active sessions. If ownerID is non-empty, filters by owner.
	ListSessions(ctx context.Context, ownerID string) ([]SessionInfo, error)
	// CreateSession creates a new empty session and returns its ID.
	CreateSession(ctx context.Context, ownerID string, env map[string]string) (string, error)
	// DestroySession destroys a session by ID.
	// If ownerID is non-empty, verifies ownership before destroying.
	DestroySession(ctx context.Context, sessionID, ownerID string) error
	// CanCreateSession checks if a new session can be created.
	// Returns (canCreate, currentCount, maxAllowed).
	CanCreateSession(ctx context.Context, ownerID string) (bool, int, int)
	// SessionsEnabled returns whether sessions are enabled.
	SessionsEnabled() bool
}

Service defines the interface for sandbox code execution backends.

func New

New creates a new sandbox service based on the configuration.

type Session

type Session struct {
	ID        string
	OwnerID   string // Optional owner ID for session binding.
	Handle    string
	CreatedAt time.Time
	LastUsed  time.Time
	Env       map[string]string
}

Session is the backend-agnostic record of a persistent sandbox session; Handle is the backing resource (docker container ID or direct workspace directory).

type SessionFile

type SessionFile struct {
	Name     string    `json:"name"`
	Size     int64     `json:"size"`
	Modified time.Time `json:"modified"`
}

SessionFile represents a file in the session workspace.

type SessionInfo

type SessionInfo struct {
	ID             string        `json:"session_id"`
	CreatedAt      time.Time     `json:"created_at"`
	LastUsed       time.Time     `json:"last_used"`
	TTLRemaining   time.Duration `json:"ttl_remaining"`
	WorkspaceFiles []SessionFile `json:"workspace_files"`
}

SessionInfo represents information about an active session.

type SessionManager

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

SessionManager owns session lifecycle policy; records live in the SessionStore, only lastUsed + in-flight counts are in memory (lost on restart → fresh TTLs).

func NewSessionManager

func NewSessionManager(cfg config.SessionConfig, log logrus.FieldLogger, store SessionStore) *SessionManager

NewSessionManager creates a new session manager backed by the given store.

func (*SessionManager) ActiveSessionCount

func (m *SessionManager) ActiveSessionCount(ctx context.Context) int

ActiveSessionCount returns the count of active sessions.

func (*SessionManager) CanCreateSession

func (m *SessionManager) CanCreateSession(ctx context.Context, ownerID string) (bool, int, int)

CanCreateSession reports whether a new session can be created, counting only ownerID's sessions when set. Returns (canCreate, currentCount, maxAllowed).

func (*SessionManager) Destroy

func (m *SessionManager) Destroy(ctx context.Context, sessionID, ownerID string) error

Destroy removes a session and reaps its backing resource. If ownerID is non-empty, verifies ownership before destroying.

func (*SessionManager) Enabled

func (m *SessionManager) Enabled() bool

Enabled returns whether sessions are enabled.

func (*SessionManager) GenerateSessionID

func (m *SessionManager) GenerateSessionID() string

GenerateSessionID creates a new session ID. The caller is responsible for associating this with a backing resource.

func (*SessionManager) Get

func (m *SessionManager) Get(ctx context.Context, sessionID string, ownerID string) (*Session, error)

Get retrieves a session by ID and updates its last used timestamp. ownerID is optional - when provided, ownership is verified.

func (*SessionManager) GetLastUsed

func (m *SessionManager) GetLastUsed(sessionID string) time.Time

GetLastUsed returns the last used time for a session. Returns the zero time if the session hasn't been accessed since server start.

func (*SessionManager) List added in v0.38.0

func (m *SessionManager) List(ctx context.Context) ([]*Session, error)

List returns all live sessions from the store. Callers layer their own per-backend view (e.g. workspace files) on top.

func (*SessionManager) MaxSessions

func (m *SessionManager) MaxSessions() int

MaxSessions returns the configured maximum number of sessions.

func (*SessionManager) RecordAccess

func (m *SessionManager) RecordAccess(sessionID string)

RecordAccess records an access time for a session (for TTL tracking).

func (*SessionManager) Start

func (m *SessionManager) Start(ctx context.Context) error

Start begins the background cleanup goroutine.

func (*SessionManager) Stop

func (m *SessionManager) Stop(ctx context.Context) error

Stop terminates the cleanup goroutine and destroys all active sessions.

func (*SessionManager) TTLRemaining

func (m *SessionManager) TTLRemaining(sessionID string) time.Duration

TTLRemaining returns the time remaining until the session expires from inactivity. Returns the full TTL if session hasn't been accessed yet (e.g., after server restart).

type SessionStore added in v0.38.0

type SessionStore interface {
	// Get returns the session with the given ID, or (nil, nil) when it does not
	// exist. A non-nil error is a lookup failure, not a missing session.
	Get(ctx context.Context, sessionID string) (*Session, error)
	// List returns all live sessions.
	List(ctx context.Context) ([]*Session, error)
	// Remove tears down the session's backing resource (container / workspace).
	Remove(ctx context.Context, session *Session) error
}

SessionStore is the backend-specific half of session management (record persistence + teardown); all policy lives in SessionManager. Returns snapshots.

Jump to

Keyboard shortcuts

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