sandbox

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

View Source
const (
	MetadataRequestKey = "sandbox_request"
	MetadataPreviewKey = "sandbox_preview"
)

Variables

View Source
var ErrDaggerNotImplemented = errors.New(
	"dagger sandbox backend: not implemented — see GitHub issue for roadmap",
)

ErrDaggerNotImplemented is returned by all DaggerExecutor methods until the Dagger backend is fully implemented (see GitHub issue for the roadmap). This sentinel lets callers distinguish "backend not built yet" from other errors so they can fall back to NoopExecutor gracefully.

Functions

func BuildToolPermissionRequest

func BuildToolPermissionRequest(req PermissionRequest, opts ToolPermissionOptions) (types.ToolPermissionRequest, error)

BuildToolPermissionRequest converts a normalized sandbox request into the runtime permission request expected by the engine.

func EnvSliceToMap

func EnvSliceToMap(env []string) map[string]string

enviroToMap converts an os.Environ()-style slice to a map. Exported for use in tests.

func ErrorForDecision

func ErrorForDecision(result DecisionResult) error

ErrorForDecision converts a normalized decision into a conventional Go error.

func ErrorForPermissionResult

func ErrorForPermissionResult(result types.PermissionResult, fallbackReason string) error

ErrorForPermissionResult converts a runtime permission result into a conventional Go error.

func ResolveToolPermission

func ResolveToolPermission(
	ctx context.Context,
	checker types.CanUseToolFn,
	req PermissionRequest,
	opts ToolPermissionOptions,
) (types.PermissionResult, error)

ResolveToolPermission runs the normalized request through the active permission resolver.

Types

type AccessKind

type AccessKind string

AccessKind describes the resource access being requested.

const (
	AccessRead     AccessKind = "read"
	AccessWrite    AccessKind = "write"
	AccessCreate   AccessKind = "create"
	AccessDelete   AccessKind = "delete"
	AccessSearch   AccessKind = "search"
	AccessExecute  AccessKind = "execute"
	AccessNetwork  AccessKind = "network"
	AccessEscalate AccessKind = "escalate"
)

type ApprovalRequiredError

type ApprovalRequiredError struct {
	Reason string
}

ApprovalRequiredError is returned when an action is valid but needs approval.

func (*ApprovalRequiredError) Error

func (e *ApprovalRequiredError) Error() string

type ApprovalScope

type ApprovalScope string

ApprovalScope controls how long an approval grant should remain valid.

const (
	ApprovalScopeToolCall ApprovalScope = "tool_call"
	ApprovalScopeTurn     ApprovalScope = "turn"
	ApprovalScopeSession  ApprovalScope = "session"
)

type CommandPolicy

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

CommandPolicy is the single authoritative command safety classifier. It replaces the scattered PermissionValidator in the bash package.

Two-tier model (mirrors Codex):

  • IsKnownSafe: explicit allowlist → bypass approval entirely
  • Evaluate: deny/ask/allow based on danger fragments and command type

func NewDefaultCommandPolicy

func NewDefaultCommandPolicy() *CommandPolicy

func (*CommandPolicy) Evaluate

func (p *CommandPolicy) Evaluate(command string) DecisionResult

Evaluate returns the policy decision for a command.

Order of evaluation:

  1. Known-safe allowlist → Allow (no approval prompt needed)
  2. Shell wrapper → evaluate composed inner
  3. Deny fragment match → Deny
  4. Ask-command list → Ask
  5. Command type classification: write/vcs/unknown → Ask, read/search/state → Allow

func (*CommandPolicy) IsKnownSafe

func (p *CommandPolicy) IsKnownSafe(command string) bool

IsKnownSafe returns true when every sub-command in the expression is on the explicit safe allowlist, meaning the entire command can be executed without any approval prompt.

Mirrors Codex's is_known_safe_command. If the command is a shell wrapper (bash -c "…") the inner script is split into segments and each is checked.

type Context

type Context struct {
	WorkingDirectory string
	WorkspaceRoot    string
	AdditionalRoots  []string
	Environment      EnvironmentKind
	SandboxEnabled   bool
}

Context carries the execution boundary relevant to sandbox decisions.

func (Context) ResolvePath

func (c Context) ResolvePath(path string) (string, error)

ResolvePath resolves a candidate path according to the current execution context. When a WorkspaceRoot is configured, relative paths are anchored to the root and validated to stay inside it. Absolute paths bypass workspace containment so the user can approve writes to /tmp or other out-of-workspace directories — the FilesystemPolicy dangerous-prefix check still applies downstream.

type DaggerExecutor

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

DaggerExecutor runs commands inside isolated OCI containers managed by the Dagger engine (https://dagger.io). Each agent session gets its own container environment; changes are tracked as git commits so the full history is recoverable.

┌─────────────────────────────────────────────────────────────────────┐ │ STATUS: STUB — methods return ErrDaggerNotImplemented │ │ │ │ Implementation is tracked in GitHub issue #XX. │ │ DO NOT implement pieces here without first reading the issue. │ └─────────────────────────────────────────────────────────────────────┘

Architecture overview (to be implemented):

DaggerExecutor
  │
  ├─ dagger.Connect(ctx) → *dagger.Client          [once per session]
  │
  ├─ environments map[string]*Environment           [per environment ID]
  │     └─ container  *dagger.Container             [persistent across calls]
  │     └─ gitRepo    *repository.Repository        [tracks file changes]
  │
  └─ Run(ctx, req) → RunResult
        ├─ resolve or create Environment
        ├─ container.WithExec(req.Command)
        ├─ capture stdout/stderr
        ├─ persist mutated container state
        └─ if req.Dagger.ExposePorts: tunnel via dag.Host().Tunnel()

Dependency: dagger.io/dagger (NOT yet in go.mod — add when implementing) Minimum Dagger engine version: v0.18+

func (*DaggerExecutor) Healthy

func (e *DaggerExecutor) Healthy(_ context.Context) error

Healthy checks that the Dagger engine is reachable. Stub: always returns ErrDaggerNotImplemented.

func (*DaggerExecutor) Kind

func (e *DaggerExecutor) Kind() EnvironmentKind

func (*DaggerExecutor) Run

Run executes a shell command inside the Dagger container environment. Stub: always returns ErrDaggerNotImplemented.

type DaggerExecutorConfig

type DaggerExecutorConfig struct {
	// BaseImage is the OCI image used for new environments.
	// Default: "ubuntu:24.04"
	BaseImage string

	// SetupCommands are shell commands run once when building the base image.
	// Example: ["apt-get update -y", "apt-get install -y python3 nodejs"]
	SetupCommands []string

	// WorkDir is the working directory inside the container.
	// Default: "/workdir"
	WorkDir string

	// Env is a set of KEY=VALUE pairs always injected into the container.
	Env map[string]string

	// StorageDir is the host directory where environment state is persisted.
	// Default: ~/.config/seshat/sandbox/
	StorageDir string

	// NetworkAccess controls whether the container can reach the internet.
	// Default: true (containers have internet access by default).
	// Set to false for fully air-gapped sandboxes.
	NetworkAccess bool
}

DaggerExecutorConfig holds static configuration for the DaggerExecutor. All fields have sensible defaults and can be overridden by the operator.

func DefaultDaggerConfig

func DefaultDaggerConfig() DaggerExecutorConfig

DefaultDaggerConfig returns a DaggerExecutorConfig with sensible defaults.

type DaggerRunOptions

type DaggerRunOptions struct {
	// ExposePorts lists container ports to tunnel back to the host.
	// The RunResult.Endpoints map is populated with host:port mappings.
	ExposePorts []int

	// EnvironmentID selects a named, persistent environment.
	// Empty means the default session environment.
	EnvironmentID string
}

DaggerRunOptions carries Dagger-specific per-run parameters. These are only meaningful when the active Executor is a DaggerExecutor.

type Decision

type Decision string

Decision is the normalized outcome of a sandbox/policy check.

const (
	DecisionAllow Decision = "allow"
	DecisionAsk   Decision = "ask"
	DecisionDeny  Decision = "deny"
)

type DecisionResult

type DecisionResult struct {
	Decision Decision
	Reason   string
}

DecisionResult is the normalized output of a policy decision.

type EnvironmentKind

type EnvironmentKind string

EnvironmentKind describes where a tool executes. The policy layer should know this, even if the actual backend runtime (local process, docker, remote host) is implemented elsewhere.

const (
	EnvironmentLocal    EnvironmentKind = "local"
	EnvironmentWorktree EnvironmentKind = "worktree"
	EnvironmentDocker   EnvironmentKind = "docker"
	EnvironmentRemote   EnvironmentKind = "remote"
	EnvironmentUnknown  EnvironmentKind = "unknown"
)

type Executor

type Executor interface {
	// Run executes a command inside the sandbox and streams its output.
	// Implementations must respect ctx cancellation and RunRequest.Timeout.
	Run(ctx context.Context, req RunRequest) (RunResult, error)

	// Kind identifies which backend this is.
	Kind() EnvironmentKind

	// Healthy returns nil when the executor is ready to accept work.
	// A non-nil error means the backend is unavailable (Dagger engine down,
	// Docker socket missing, etc). The engine calls this once at startup.
	Healthy(ctx context.Context) error
}

Executor is the interface all sandbox backends implement. The engine routes bash/shell execution through the active Executor.

Backends:

  • NoopExecutor — runs commands directly on the host OS (default, no isolation)
  • DaggerExecutor — runs commands inside an isolated Dagger/OCI container

Selection at startup via ExecutorConfig.Kind. Tools that are read-only (file read, glob, grep) bypass the Executor and go directly to the host FS.

func NewExecutor

func NewExecutor(cfg ExecutorConfig) (Executor, error)

NewExecutor creates the Executor selected by cfg.Kind. Returns an error when the requested backend is unavailable.

type ExecutorConfig

type ExecutorConfig struct {
	// Kind selects the backend. Defaults to EnvironmentLocal (noop).
	Kind EnvironmentKind

	// Dagger holds Dagger-specific configuration.
	// Only used when Kind == EnvironmentDocker.
	Dagger DaggerExecutorConfig
}

ExecutorConfig is the configuration passed to NewExecutor at startup.

type FilesystemPolicy

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

FilesystemPolicy centralizes common filesystem access checks.

func NewDefaultFilesystemPolicy

func NewDefaultFilesystemPolicy() *FilesystemPolicy

func (*FilesystemPolicy) EvaluatePath

func (p *FilesystemPolicy) EvaluatePath(ctx Context, path string, access AccessKind) (PathDecision, error)

type NoopExecutor

type NoopExecutor struct{}

NoopExecutor runs commands directly on the host OS with no additional isolation. This is the default executor and mirrors the current behavior of the bash tool before sandboxing was introduced.

Isolation: none. The CommandPolicy and FilesystemPolicy still apply, but there is no OS-level boundary between the agent and the host filesystem.

func NewNoopExecutor

func NewNoopExecutor() *NoopExecutor

NewNoopExecutor returns a NoopExecutor ready to use.

func (*NoopExecutor) Healthy

func (e *NoopExecutor) Healthy(_ context.Context) error

func (*NoopExecutor) Kind

func (e *NoopExecutor) Kind() EnvironmentKind

func (*NoopExecutor) Run

func (e *NoopExecutor) Run(ctx context.Context, req RunRequest) (RunResult, error)

type PathDecision

type PathDecision struct {
	DecisionResult
	ResolvedPath string
}

PathDecision includes the resolved path for filesystem checks.

type PermissionDecision

type PermissionDecision struct {
	Decision      Decision
	Reason        string
	Scope         ApprovalScope
	ApprovedPaths []string
	Metadata      map[string]any
}

PermissionDecision is the normalized response returned by the sandbox/approval layer.

func (PermissionDecision) IsAllowed

func (d PermissionDecision) IsAllowed() bool

IsAllowed reports whether the request was approved.

type PermissionDeniedError

type PermissionDeniedError struct {
	Reason string
}

PermissionDeniedError is returned when the sandbox denies an action outright.

func (*PermissionDeniedError) Error

func (e *PermissionDeniedError) Error() string

type PermissionRequest

type PermissionRequest struct {
	ToolName       string
	Description    string
	Environment    EnvironmentKind
	Access         AccessKind
	Command        string
	Paths          []string
	NetworkTargets []string
	Justification  string
	Scope          ApprovalScope
	Metadata       map[string]any
}

PermissionRequest is the normalized request emitted by tools and runtimes when they need a sandbox/approval decision.

func (PermissionRequest) DescriptionText

func (r PermissionRequest) DescriptionText() string

DescriptionText returns a stable approval-friendly description.

func (PermissionRequest) MetadataMap

func (r PermissionRequest) MetadataMap() map[string]any

MetadataMap returns stable metadata for the shared permission pipeline.

func (PermissionRequest) Validate

func (r PermissionRequest) Validate() error

Validate ensures a permission request is structurally usable by the approval pipeline.

type RunRequest

type RunRequest struct {
	// Command is the shell command string to execute.
	// Passed as-is to the interpreter defined by Shell.
	Command string

	// Shell specifies the interpreter (default: /bin/sh -c).
	// Ignored by DaggerExecutor which always uses sh inside the container.
	Shell []string

	// Env is a set of KEY=VALUE pairs injected into the command environment
	// on top of the executor's base environment.
	Env map[string]string

	// WorkDir is the working directory for the command.
	// For DaggerExecutor, this is relative to the container's /workdir.
	WorkDir string

	// Stdin is connected to the command's standard input (may be nil).
	Stdin io.Reader

	// Timeout is the maximum duration for the command.
	// Zero means no timeout beyond the parent context deadline.
	Timeout time.Duration

	// Background requests a fire-and-forget execution (no output captured).
	// Used for long-running processes (servers, watchers).
	Background bool

	// Dagger-specific extensions — ignored by NoopExecutor.
	Dagger *DaggerRunOptions
}

RunRequest is the input to Executor.Run.

type RunResult

type RunResult struct {
	// Stdout and Stderr capture the command output.
	// Empty when Background=true.
	Stdout string
	Stderr string

	// ExitCode is the process exit code. Non-zero indicates failure.
	ExitCode int

	// Duration is the wall-clock time the command took.
	Duration time.Duration

	// Endpoints maps exposed port numbers to "host:port" strings.
	// Populated by DaggerExecutor when DaggerRunOptions.ExposePorts is set.
	Endpoints map[int]string
}

RunResult is the output of Executor.Run.

type ToolAccessPreview

type ToolAccessPreview struct {
	ToolName       string   `json:"tool_name"`
	Environment    string   `json:"environment,omitempty"`
	Access         string   `json:"access,omitempty"`
	Command        string   `json:"command,omitempty"`
	Paths          []string `json:"paths,omitempty"`
	NetworkTargets []string `json:"network_targets,omitempty"`
	Justification  string   `json:"justification,omitempty"`
}

ToolAccessPreview is the normalized human-facing summary of what a tool is asking to do.

func BuildPreview

func BuildPreview(req PermissionRequest) ToolAccessPreview

BuildPreview converts a permission request into a stable preview payload.

type ToolPermissionOptions

type ToolPermissionOptions struct {
	ToolInput              map[string]any
	ToolUseID              string
	SessionID              types.SessionID
	TurnID                 types.TurnID
	PermissionMode         types.PermissionMode
	WorkingDirectory       string
	IsToolRunningInSandbox bool
	Stage                  types.ToolPermissionStage
	Intent                 types.ToolPermissionIntent
	Metadata               map[string]any
}

ToolPermissionOptions carries runtime-specific fields for the shared permission pipeline request.

Jump to

Keyboard shortcuts

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