sandbox

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const (
	StreamTypeStdout = "stdout"
	StreamTypeStderr = "stderr"
	StreamTypeExit   = "exit"
	StreamTypeKill   = "kill"
)

StreamChunk is one unit in the streaming host-command protocol (Streaming=true).

Broker → Shim: type "stdout"/"stderr" carry Data; type "exit" carries ExitCode. Shim → Broker: type "kill" requests SIGTERM on the host process group.

Variables

This section is empty.

Functions

func BuildEnv

func BuildEnv(cfg SandboxConfig, proxyPort int) map[string]string

BuildEnv generates the environment variables for a sandbox.

func CheckPolicy

func CheckPolicy(def CommandDef, args []string) bool

func CommandFromArgv0

func CommandFromArgv0(argv0 string) string

func Prepare

func Prepare(spec Spec) (string, error)

Prepare writes the 3 scripts (outer / setup / inner) to /tmp and returns the path to the outer script. The caller is responsible for actually running it (via exec.Cmd, syscall.Exec, or a runtime abstraction).

func RunGitShim

func RunGitShim(args []string) (int, error)

func ShellQuote

func ShellQuote(value string) string

ShellQuote is the exported version of shellQuote for use outside the package.

func ShimLinks(commands []string) map[string]string

ShimLinks returns the symlinks to create for host commands.

Types

type BasicMountEntry

type BasicMountEntry struct {
	Source   string
	Target   string
	ReadOnly bool
}

BasicMountEntry describes a bind mount for the sandbox.

func BuildMounts

func BuildMounts(cfg SandboxConfig) []BasicMountEntry

BuildMounts generates the mount entries for a sandbox configuration.

type BindMount

type BindMount struct {
	Source   string
	Target   string // if empty, defaults to Source
	Mode     string // "rw" | "" (ro default)
	IsFile   bool   // if true, treat target as a file (touch before bind, skip type detection)
	Optional bool   // if true, skip mount when Source does not exist on the host
}

BindMount is the dispatcher-facing DTO for arbitrary bind-mount requests. It is used by the dispatcher boundary (via SandboxSpec.AdditionalBindings) and is converted into Mount entries at the edge. The sandbox layer itself works in terms of Mount only.

type BoidExecutor

type BoidExecutor interface {
	ExecuteBoidBuiltin(ctx TokenContext, req *BoidRequest) *ExecResponse
}

BoidExecutor performs typed boid builtin operations after broker validation.

type BoidOp

type BoidOp string
const (
	BoidOpJobDone    BoidOp = "job_done"
	BoidOpJobList    BoidOp = "job_list"
	BoidOpJobShow    BoidOp = "job_show"
	BoidOpJobLog     BoidOp = "job_log"
	BoidOpActionSend BoidOp = "action_send"
	BoidOpAgentStop  BoidOp = "agent_stop"
	BoidOpTaskCreate BoidOp = "task_create"
	BoidOpTaskGet    BoidOp = "task_get"
	BoidOpTaskUpdate BoidOp = "task_update"
	BoidOpTaskImport BoidOp = "task_import"
	BoidOpTaskReopen BoidOp = "task.reopen"
	BoidOpTaskList   BoidOp = "task_list"
	BoidOpTaskNotify BoidOp = "task_notify"
	BoidOpTaskAnswer BoidOp = "task_answer"
	BoidOpTaskDelete BoidOp = "task_delete"
)

type BoidRequest

type BoidRequest struct {
	Op        BoidOp `json:"op"`
	JobID     string `json:"job_id,omitempty"`
	TaskID    string `json:"task_id,omitempty"`
	TaskField string `json:"task_field,omitempty"`
	// ProjectID is extracted by the shim for broker authorization / project
	// resolver; it is also present inside CreatePatch when the YAML includes
	// project_id. The executor prefers createReq.ProjectID from CreatePatch
	// and falls back to this field, then to ctx.ProjectID.
	ProjectID string `json:"project_id,omitempty"`

	ExitCode int    `json:"exit_code,omitempty"`
	Output   string `json:"output,omitempty"`

	// CreatePatch is a JSON-serialised api.CreateTaskRequest. The shim builds
	// it from the full YAML map so that every field (including previously
	// dropped ones such as instructions, traits, readonly, worktree,
	// branch_prefix, id) is forwarded without enumeration.
	CreatePatch json.RawMessage `json:"create_patch,omitempty"`

	// UpdatePatch is a JSON-serialised api.UpdateTaskRequest. The shim
	// assembles it from --patch-file and/or individual flags (--title,
	// --description, --payload-file).
	UpdatePatch json.RawMessage `json:"update_patch,omitempty"`

	// task import fields
	ImportTasks           []json.RawMessage `json:"import_tasks,omitempty"`
	ImportProjectOverride string            `json:"import_project_override,omitempty"`

	// task list fields
	WorkspaceID string `json:"workspace_id,omitempty"`
	Status      string `json:"status,omitempty"`
	Limit       int    `json:"limit,omitempty"`

	// task notify fields
	Message    string `json:"message,omitempty"`
	Ask        string `json:"ask,omitempty"`
	QuestionID string `json:"question_id,omitempty"`
	SessionID  string `json:"session_id,omitempty"`
	Progress   string `json:"progress,omitempty"`
	Done       string `json:"done,omitempty"`
	Fail       string `json:"fail,omitempty"`

	// task answer fields
	Answer string `json:"answer,omitempty"`

	// task delete fields
	Force bool `json:"force,omitempty"`

	// action send fields
	ActionType string `json:"action_type,omitempty"`
	Payload    []byte `json:"payload,omitempty"`
}

type Broker

type Broker struct {
	SocketPath      string
	BoidBinary      string
	BoidExecutor    BoidExecutor
	ProjectResolver ProjectResolver
	// contains filtered or unexported fields
}

func (*Broker) GetContext

func (b *Broker) GetContext(token string) (TokenContext, bool)

func (*Broker) Handle

func (b *Broker) Handle(req *ExecRequest) *ExecResponse

func (*Broker) Register

func (b *Broker) Register(commands map[string]CommandDef, builtinPolicies map[string]BuiltinPolicy, ctx TokenContext) string

func (*Broker) RegisterWithSecrets

func (b *Broker) RegisterWithSecrets(commands map[string]CommandDef, builtinPolicies map[string]BuiltinPolicy, ctx TokenContext, resolver SecretResolver) string

func (*Broker) Start

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

func (*Broker) Stop

func (b *Broker) Stop()

func (*Broker) Unregister

func (b *Broker) Unregister(token string)

type BuiltinPolicy

type BuiltinPolicy struct {
	AllowedOps map[string]struct{}
	// AllowedCwdRoots lists additional cwd roots permitted for this builtin
	// beyond the per-token entry root (project/worktree dir). Used so that
	// e.g. gate jobs can target /tmp or the host project dir without the
	// broker needing to know the role itself.
	AllowedCwdRoots []string
}

BuiltinPolicy defines which operations are permitted for a named builtin command. It is stamped at token registration time by the planner and checked at dispatch time by the broker, keeping all role-based authorization logic outside the broker itself.

func (BuiltinPolicy) Allows

func (p BuiltinPolicy) Allows(op string) bool

Allows reports whether op is in the allowed set.

func (BuiltinPolicy) AllowsCwd

func (p BuiltinPolicy) AllowsCwd(cwd string) bool

AllowsCwd reports whether cwd is within any of the policy's additional cwd roots.

type CommandDef

type CommandDef struct {
	Name               string
	Path               string
	AllowedPatterns    []string
	DeniedPatterns     []string
	AllowedSubcommands []string
	AllowStdin         bool
	Env                map[string]string
}

CommandDef is the canonical sandbox-side policy for brokered host commands. Dispatcher and orchestrator mirror this shape as transport data, but the policy semantics live here.

type ExecRequest

type ExecRequest struct {
	Command   string       `json:"command"`
	Args      []string     `json:"args"`
	Cwd       string       `json:"cwd,omitempty"`
	Stdin     []byte       `json:"stdin,omitempty"`
	Token     string       `json:"token"`
	Boid      *BoidRequest `json:"boid,omitempty"`
	Git       *GitRequest  `json:"git,omitempty"`
	Streaming bool         `json:"streaming,omitempty"`
}

type ExecResponse

type ExecResponse struct {
	ExitCode int    `json:"exit_code"`
	Stdout   string `json:"stdout"`
	Stderr   string `json:"stderr"`
}

func RunBoidShim

func RunBoidShim(args []string) (*ExecResponse, error)

func ShimExec

func ShimExec(brokerSocket, argv0 string, args []string, stdin []byte) (*ExecResponse, error)

ShimExec sends a host command request to the broker using the streaming protocol so that stdout/stderr is forwarded in real-time and signals (Ctrl-C) are propagated to the host process group.

type FileWrite

type FileWrite struct {
	Path    string // absolute path inside sandbox
	Content string
}

FileWrite describes a file to materialize inside the sandbox. Content is written verbatim (shell-quoted at render time); the parent directory is created with mkdir -p beforehand.

type GitBinding

type GitBinding struct {
	ProjectDir     string
	WorktreeRoot   string
	SnapshotBranch string
	Upstream       GitUpstream
	Remotes        map[string]GitRemote
}

type GitOp

type GitOp string
const (
	GitOpFetch      GitOp = "fetch"
	GitOpPush       GitOp = "push"
	GitOpPushDelete GitOp = "push_delete"
	GitOpCloneLocal GitOp = "clone_local"
)

type GitRemote

type GitRemote struct {
	FetchURL string
	PushURL  string
}

type GitRequest

type GitRequest struct {
	Op             GitOp    `json:"op"`
	Remote         string   `json:"remote,omitempty"`
	Refspecs       []string `json:"refspecs,omitempty"`
	DryRun         bool     `json:"dry_run,omitempty"`
	Verbose        bool     `json:"verbose,omitempty"`
	Quiet          bool     `json:"quiet,omitempty"`
	Prune          bool     `json:"prune,omitempty"`
	Tags           bool     `json:"tags,omitempty"`
	Force          bool     `json:"force,omitempty"`
	Porcelain      bool     `json:"porcelain,omitempty"`
	ForceWithLease bool     `json:"force_with_lease,omitempty"`
	Delete         bool     `json:"delete,omitempty"`
	// Source and Dest are used exclusively for GitOpCloneLocal.
	// Source is the peer project path to clone from; Dest is the destination
	// directory within the current worktree. Both are validated by the broker.
	Source string `json:"source,omitempty"`
	Dest   string `json:"dest,omitempty"`
}

type GitUpstream

type GitUpstream struct {
	Remote   string
	MergeRef string
}

type HookFile

type HookFile struct {
	Source     string // host-side absolute path
	TargetName string // filename inside sandbox .boid/hooks/
}

HookFile describes a single hook file to bind-mount into the sandbox. Retained as a helper shape; dispatcher composes these into Spec.Mounts.

type Mount

type Mount struct {
	Source     string // host path (empty for tmpfs)
	Target     string // absolute path inside sandbox
	Type       MountType
	ReadOnly   bool
	Slave      bool     // mount --make-rslave after mounting
	IsFile     bool     // target is a file, not a directory
	DetectType bool     // detect file vs dir at runtime (if/elif)
	Guard      string   // shell test expression; if non-empty, wrap in if [ $Guard ]; then
	NeedsDirs  []string // subdirs to create under Target before ro remount
}

Mount describes a single filesystem mount applied inside the sandbox. Types: bind, rbind, tmpfs. Flags cover read-only remount, file vs dir handling, slave propagation, guards, and required sub-directory creation.

type MountType

type MountType string

MountType represents the type of filesystem mount.

const (
	MountBind  MountType = "bind"
	MountRBind MountType = "rbind"
	MountTmpfs MountType = "tmpfs"
)

type ProjectResolver

type ProjectResolver func(ref string) (uuid string, err error)

ProjectResolver maps a project reference (UUID, exact name, or partial name) to a concrete project UUID. The broker calls it just before the UUID-based AllowsProject authorization check so that sandbox-side callers (e.g. plan agents) can use project names while the broker continues to enforce workspace isolation in UUID space. When nil, the broker passes refs through verbatim (tests and UUID-only callers).

type Proxy

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

func NewProxy

func NewProxy(allowedDomains []string) *Proxy

func WireProxy

func WireProxy(allowedDomains []string) *Proxy

func (*Proxy) Start

func (p *Proxy) Start(ctx context.Context) (int, error)

func (*Proxy) Stop

func (p *Proxy) Stop()

type Sandbox

type Sandbox interface {
	Setup(cfg SandboxConfig) error
	Shell(windowName string, cmd string) (string, error)
	Cleanup() error
}

Sandbox provides an isolated execution environment.

type SandboxConfig

type SandboxConfig struct {
	ProjectDir    string
	WorkspaceDirs map[string]string // project-id -> dir (readonly mounts)
	TaskFile      string
	HostCommands  []string
	Bindings      []string
	Env           map[string]string
	BoidBinary    string
	BrokerSocket  string
	ServerSocket  string
}

SandboxConfig holds configuration for sandbox setup.

type SecretResolver

type SecretResolver func(key string) (string, error)

type Spec

type Spec struct {
	// ID is used to namespace the generated /tmp/boid-<ID>-{outer,setup,inner}.sh files.
	ID string

	// --- Filesystem primitives ---
	// Mounts are applied in order to compose the sandbox root filesystem.
	Mounts []Mount
	// Files are materialized inside the sandbox before the entry command runs.
	Files []FileWrite
	// Symlinks are created inside the sandbox (e.g. /opt/boid/bin/<cmd> → boid).
	Symlinks []Symlink

	// --- Network ---
	// ProxyPort, when > 0, engages the nft drop policy + HTTP proxy env vars.
	ProxyPort int

	// --- Process ---
	// Argv is the program and arguments to invoke (POSIX argv).
	Argv []string
	// WorkDir is the cwd for the entry process.
	WorkDir string
	// Env is exported before the entry command runs.
	Env map[string]string
	// StdinBytes, when non-empty, is piped into the entry's stdin.
	StdinBytes []byte
	// StdoutCaptureFile, when non-empty, redirects stdout to that sandbox-internal path.
	StdoutCaptureFile string
	// ExitScript is wrapped in `trap '<ExitScript>' EXIT`. Empty = no trap (suits exec-replaced shells).
	ExitScript string
	// TTY, when true, preserves the caller's TTY through pasta so Argv can run as a full terminal app.
	TTY bool

	// --- Bookkeeping ---
	// RootDir, if non-empty, is used as the sandbox ROOT directory so Go-side
	// cleanup can remove it after exit. If empty, setup script creates one with mktemp.
	RootDir string
	// CleanupPaths are removed by outer.sh after pasta returns (used for staging dirs).
	// Removal runs outside the sandbox mount namespace, so still-active bind
	// mounts cannot cause rm to traverse onto host files.
	CleanupPaths []string
}

Spec describes a sandbox invocation in primitives only. The sandbox layer knows nothing about Role / Task / Job / Broker / Gate / Hook — all of those are the caller's concern. Everything needed to build and run the sandbox must already be present as mounts, files, env, argv, etc.

type StreamChunk

type StreamChunk struct {
	Type     string `json:"type"`
	Data     string `json:"data,omitempty"`
	ExitCode int    `json:"exit_code,omitempty"`
}
type Symlink struct {
	LinkPath   string // absolute path inside sandbox (where the symlink is created)
	LinkTarget string // what the symlink points to (resolved relative to LinkPath)
}

Symlink describes a symlink to create inside the sandbox.

type TokenContext

type TokenContext struct {
	JobID             string
	TaskID            string
	ProjectID         string
	WorkspaceID       string
	AllowedProjectIDs []string
	Role              string
	// ProjectDir is the project's host-side work directory. Independent of
	// spec.Visibility.ProjectDir (which drives sandbox mount layout and is
	// intentionally empty for gate jobs): host-side operations the broker
	// performs on behalf of the sandbox (git binding, host-command cwd) have
	// their own notion of "which project are we operating on" that doesn't
	// care whether the sandbox itself can see the tree.
	ProjectDir  string
	WorktreeDir string
	// WorkspacePeers maps peer project IDs to their host-side work directories.
	// Used by the broker to validate git clone --local source paths: only paths
	// within a known peer project are permitted as clone sources.
	WorkspacePeers map[string]string
}

func (TokenContext) AllowsProject

func (c TokenContext) AllowsProject(projectID string) bool

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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