Documentation
¶
Overview ¶
Package sandbox confines the host processes Pando spawns on the agent's behalf (the bash tool's persistent shell first; ACP terminals, skill CLI tools, MCP servers and subagents as configured) to a filesystem, network and environment policy. Pando's own process is never confined: wrapping is per spawn, so the database, KB, WebUI and desktop keep working and the policy can change at run time.
Shape ¶
- Policy (policy.go) is the platform-neutral description of what a child may do. Resolve / ResolveConfig (resolve.go) build it from config.SandboxConfig and the workspace; Policy.Hash identifies it so a spawn site can tell when a long-lived child must be re-spawned.
- Wrapper (sandbox.go) rewrites an *exec.Cmd so the command runs under a Policy, and reports a Capability: which backend is in use and whether it is actually enforced on this machine. Default returns the per-OS wrapper.
- ScrubEnv (env.go) removes credential-looking variables from a child's environment according to Policy.Env.
- Current / CurrentPolicyHash / Active / AutoAllowBash (sandbox.go) are the accessors spawn sites and UIs use for the live configuration.
- Guarantees (guarantees.go) says whether an enforced sandbox is complete (protected paths enforced, Pando's own ports blocked); AutoAllowBash requires it. RegisterGuardedPort / GuardedPorts front the leaf package internal/sandbox/portguard, where every Pando listener records its TCP port so Policy.DenyConnectPorts keeps confined commands away from it.
Precedence ¶
enterprise lock on sandbox.* > PANDO_SANDBOX env > project config > global config > default (workspace-write, network allowed, bash auto-allowed while enforced). The project-only-tightens rule is applied by internal/config at load time; the env override and lock check are applied here, by Resolve.
Per-OS backends ¶
Each OS has exactly one file defining platformWrapper(), selected by the file-name build constraint, so the backend stories never touch each other's files or this package's shared code:
- wrapper_linux.go — PANDO-US-0041: Landlock + seccomp via the `pando __sandbox-exec` re-exec helper, optional bwrap. The helper itself lives in the leaf package internal/sandbox/helper (stdlib and x/sys only) and dispatches from its init(), before any other package initialises; bwrap_linux.go builds the bwrap prefix.
- sbpl.go — pure SBPL profile generator used by the darwin backend, build-tag free so its golden tests run on every OS.
- wrapper_darwin.go — PANDO-US-0042: /usr/bin/sandbox-exec with a generated SBPL profile.
- wrapper_windows.go — PANDO-US-0043: not enforced; Job Object containment.
- wrapper_other.go — every other GOOS (//go:build !linux && !darwin && !windows): always the not-enforced no-op wrapper.
PANDO-US-0043 also adds two small helpers that cross every OS, used by the shell integration and status/log code rather than by Wrap itself:
- AttachProcessTree (jobobject_windows.go / jobobject_other.go) puts a started child in a Windows Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE so the whole tree dies with Pando; a no-op on every other OS, since those backends already clean up their own children.
- IsWSL (wsl.go) reports Windows Subsystem for Linux for status labels and logs; WSL runs the Linux backend (wrapper_linux.go) unchanged.
Until a backend story lands, its file returns newNoopWrapper(reason), which leaves the command untouched and reports Capability{Backend: "none", Enforced: false}. Callers must therefore treat "policy enabled" and "sandbox enforced" as different things: fail open with a visible warning, and never auto-approve bash unless Active() is true.
Index ¶
- Constants
- Variables
- func Active() bool
- func AttachProcessTree(p *os.Process) (release func(), err error)
- func AutoAllowBash() bool
- func CloseEventWriter()
- func CurrentPolicyHash() string
- func Emit(_ context.Context, e Event)
- func EmitSpawn(ctx context.Context, purpose Purpose, covered bool, p Policy, c Capability)
- func GenerateSBPL(p Policy) (profile string, params []string, err error)
- func GenerateSBPLWith(p Policy, opts SBPLOptions) (profile string, params []string, err error)
- func Guarantees(p Policy, c Capability) (full bool, gaps []string)
- func GuardedPorts() []int
- func IsCoreEnvName(name string) bool
- func IsSecretEnvName(name string) bool
- func IsWSL() bool
- func RegisterGuardedPort(port int, owner string) (unregister func())
- func ResetEventsForTests()
- func SandboxExecArgs(profile string, params []string, argv []string) []string
- func ScrubEnv(env []string, p Policy) []string
- func SetDefaultForTests(w Wrapper) (restore func())
- func WrapCmd(cmd *exec.Cmd, purpose Purpose) (Policy, Capability, error)
- type BwrapPolicy
- type Capability
- type Counters
- type Denial
- type DenialKind
- type EnvInherit
- type EnvPolicy
- type Event
- type EventType
- type Mode
- type Network
- type Policy
- type Purpose
- type ResolveOptions
- type SBPLOptions
- type Source
- type Status
- type Wrapper
Constants ¶
const ( OpWrite = "write" OpRead = "read" )
Operation classifies the file-system access a denial was about.
const ( // BackendNone: nothing is enforced (unsupported OS, old kernel, backend // not implemented yet). BackendNone = "none" // BackendLandlock: Linux Landlock + seccomp via the re-exec helper. BackendLandlock = "landlock+seccomp" // BackendBwrapLandlock: Linux bubblewrap for protected paths plus Landlock. BackendBwrapLandlock = "bwrap+landlock" // BackendSeatbelt: macOS sandbox-exec with an SBPL profile. BackendSeatbelt = "seatbelt" // BackendJobObject: Windows Job Object containment (not a sandbox). BackendJobObject = "job-object" )
Backend names reported in Capability.Backend.
const ( GapNotEnforced = "not enforced" GapProtectedPaths = "protected paths inside the workspace not enforced (install bubblewrap)" GapProtectedPathsAny = "protected paths inside the workspace not enforced" GapBwrapNever = "protected paths inside the workspace not enforced (UseBwrap = never)" GapGuardedPorts = "Pando's own ports reachable (needs Landlock ABI 4, Linux 6.7+)" GapGuardedPortsAny = "Pando's own ports reachable" )
Gap descriptions returned by Guarantees. They are short: they end up in the status badge ("partial: ...").
const EnvVar = config.SandboxEnvVar
EnvVar is the environment variable overriding the sandbox mode: off | workspace-write | read-only | strict. on/true/1 enables the configured mode (workspace-write when none); false/0/disabled mean off.
Variables ¶
var DefaultExtendTo = []Purpose{PurposeACPTerminals, PurposeSkills}
DefaultExtendTo is the set of extra spawn sites always wrapped besides bash.
var ErrSBPLPath = errors.New("sandbox: path cannot be used in a Seatbelt profile")
ErrSBPLPath marks a path that cannot be expressed in an SBPL profile.
var ErrWrapFailed = errors.New("sandbox: cannot apply policy to command")
ErrWrapFailed marks a Wrap error: the command must not be started as is.
Functions ¶
func Active ¶
func Active() bool
Active reports whether commands are really confined right now: the policy is enabled AND the backend is enforced on this OS.
func AttachProcessTree ¶
AttachProcessTree is a no-op on every OS but Windows: the Linux and macOS backends already confine and clean up their own children through their Wrapper (Landlock/seccomp, sandbox-exec), so there is nothing extra to attach here. See jobobject_windows.go for the real implementation.
release is always non-nil and safe to call any number of times.
func AutoAllowBash ¶
func AutoAllowBash() bool
AutoAllowBash reports whether the bash permission prompt may be skipped: only while the sandbox is Active, gives its full guarantees (Guarantees: protected paths and Pando's own ports enforced) and auto-allow is not disabled. Callers still keep their own floor (dangerous commands, explicit approvals).
func CloseEventWriter ¶
func CloseEventWriter()
CloseEventWriter flushes and stops the background JSONL writer. Safe to call when it was never started. Callers that need every queued event durably written before reading the file back (e.g. tests) should call this first.
func CurrentPolicyHash ¶
func CurrentPolicyHash() string
CurrentPolicyHash is Current().Hash(). A long-lived sandboxed child (the persistent shell) stores it at spawn and is re-spawned when it changes.
func Emit ¶
Emit records e: a slog Info line, the in-memory ring buffer, the local JSONL log, and the pando_stats counters. It never blocks the caller (the JSONL write is handed to a background writer) and never fails visibly: this is a purely observational subsystem.
An EventUnavailable is recorded at most once per process; later calls are no-ops so a backend that stays unavailable for the whole run does not spam the log or counters on every spawn.
func EmitSpawn ¶
EmitSpawn records the outcome of wrapping one spawn for purpose at a wrap site other than the persistent shell (ACP terminals, skills, MCP stdio, sub-agents): an EventApplied, with the purpose as Reason, when covered is true and p is enabled and c enforced; an EventUnavailable (once per process) when covered but the backend cannot enforce; nothing when the spawn was not covered. It carries no command line.
func GenerateSBPL ¶
GenerateSBPL builds the Seatbelt profile for p without touching the filesystem: paths are used as given (plus their /private aliases) and every ancestor is assumed to exist. It returns the profile text and the sandbox-exec parameters as "KEY=VALUE" strings, each to be passed after a "-D" flag (see SandboxExecArgs).
A disabled policy is an error: there is nothing to generate.
func GenerateSBPLWith ¶
func GenerateSBPLWith(p Policy, opts SBPLOptions) (profile string, params []string, err error)
GenerateSBPLWith is GenerateSBPL consulting the filesystem through opts: symlinked roots get their resolved form too and the ancestor rename guards only cover parents that exist.
func Guarantees ¶
func Guarantees(p Policy, c Capability) (full bool, gaps []string)
Guarantees reports whether the sandbox fully protects Pando from the commands it confines under p with backend c, and lists what is missing otherwise. Protection is only complete when
- the backend is enforced;
- protected and deny paths inside writable roots are enforced (Linux without bubblewrap cannot: .git/hooks, .git/config and deny paths stay reachable);
- Pando's own TCP ports (p.DenyConnectPorts) are blocked while the network is allowed (Linux needs Landlock ABI 4).
A command confined with gaps can still reach one of those escape hatches, so the bash tool must not skip its permission prompt (AutoAllowBash).
func GuardedPorts ¶
func GuardedPorts() []int
GuardedPorts returns the ports of this process's listeners plus those published by other live Pando processes on this machine.
func IsCoreEnvName ¶
IsCoreEnvName reports whether name is in the "core" inherit set (PATH, HOME, TERM, LANG, LC_*, SSH_AUTH_SOCK, toolchain roots, Windows system variables, ...).
func IsSecretEnvName ¶
IsSecretEnvName reports whether a variable name looks like it holds a credential: provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...), tokens (GITHUB_TOKEN, GH_TOKEN, NPM_TOKEN, AWS_SESSION_TOKEN, ...), secrets and passwords. Core variables (IsCoreEnvName) are never secret.
func IsWSL ¶
func IsWSL() bool
IsWSL reports whether this process is running under Windows Subsystem for Linux. It is informational only: WSL runs a real (WSL2) or translated (WSL1) Linux kernel, so the Linux backend (PANDO-US-0041) applies the same Landlock+seccomp policy there unchanged — IsWSL exists purely so status labels and logs can say "Linux (WSL)" instead of a bare "Linux".
It never errors: on any OS other than Linux, and on Linux outside WSL, a missing /proc entry simply means "not WSL" and IsWSL returns false.
func RegisterGuardedPort ¶
RegisterGuardedPort records a TCP port Pando itself listens on, so that sandboxed commands cannot connect to it (Policy.DenyConnectPorts). Every listener bind site calls it (or portguard.Guard, which also unregisters when the listener closes). The returned function removes the registration.
func ResetEventsForTests ¶
func ResetEventsForTests()
ResetEventsForTests clears every piece of process-wide event state: the ring buffer, the counters, the EventUnavailable once-guard and the JSONL writer. For tests only.
func SandboxExecArgs ¶
SandboxExecArgs returns the argv for /usr/bin/sandbox-exec running argv (argv[0] is the program path) under profile with params ("KEY=VALUE"): [sandbox-exec -p <profile> -D K=V ... -- argv...]. sandbox-exec parses its flags with getopt(3), so "--" ends them and a program whose name starts with "-" is still run.
func ScrubEnv ¶
ScrubEnv returns the environment a sandboxed child should receive, given the parent's env in os.Environ() form ("NAME=value"). It never modifies the input. For a disabled policy it returns an unchanged copy.
Decision order for each variable (names compared case-insensitively):
- Env.Exclude match: dropped.
- Env.Keep match: kept.
- Always dropped: DBUS_SESSION_BUS_ADDRESS (a desktop-bus escape hatch), and SSH_AUTH_SOCK when the network is restricted.
- Credential-looking (when Env.ScrubSecrets): dropped. See IsSecretEnvName.
- Env.Inherit: "all" keeps the rest, "core" keeps only IsCoreEnvName, "none" drops the rest.
Entries without a name (Windows "=C:=C:\\" drive entries) are kept.
func SetDefaultForTests ¶
func SetDefaultForTests(w Wrapper) (restore func())
SetDefaultForTests replaces the wrapper Default returns until the returned restore function is called. For tests in other packages (e.g. a fake enforced wrapper to exercise auto-allow).
func WrapCmd ¶
WrapCmd is the one-call helper for spawn sites: when the current policy covers purpose it scrubs cmd.Env (inheriting os.Environ() when nil) and wraps cmd with Default(). It returns the policy and capability it applied so the caller can record them (hash, backend) and warn when not enforced. When the policy does not cover purpose cmd is left untouched.
Types ¶
type BwrapPolicy ¶
type BwrapPolicy string
BwrapPolicy selects whether the Linux backend uses bubblewrap to enforce protected paths inside writable roots.
const ( BwrapAuto BwrapPolicy = config.SandboxBwrapAuto BwrapAlways BwrapPolicy = config.SandboxBwrapAlways BwrapNever BwrapPolicy = config.SandboxBwrapNever )
type Capability ¶
type Capability struct {
// Backend is one of the Backend* constants.
Backend string `json:"backend"`
// Version is backend specific, e.g. the Landlock ABI ("5") or the bwrap
// version. Empty when unknown.
Version string `json:"version,omitempty"`
// Enforced is true only when Wrap really confines commands.
Enforced bool `json:"enforced"`
// Reason explains a degraded or unavailable backend, for the UI and logs.
Reason string `json:"reason,omitempty"`
// ProtectsNestedPaths: protected and deny paths inside writable roots
// are enforced (read-only, deny paths hidden), however deep. False for
// Linux without bubblewrap.
ProtectsNestedPaths bool `json:"protectsNestedPaths"`
// BlocksPorts: connections to individual TCP ports
// (Policy.DenyConnectPorts) can be refused while the network is allowed.
// False for Linux with a Landlock ABI below 4.
BlocksPorts bool `json:"blocksPorts"`
}
Capability describes the sandbox backend available on this machine.
func (Capability) String ¶
func (c Capability) String() string
String renders the capability for logs and badges, e.g. "landlock+seccomp v5" or "none (windows: not supported)".
type Counters ¶
type Counters struct {
Applied uint64 `json:"applied"`
Denied uint64 `json:"denied"`
EscalationRequested uint64 `json:"escalationRequested"`
EscalationGranted uint64 `json:"escalationGranted"`
EscalationDenied uint64 `json:"escalationDenied"`
}
Counters is a snapshot of the process-wide sandbox event counts, exposed through the pando_stats tool.
func EventCounters ¶
func EventCounters() Counters
EventCounters returns a snapshot of the sandbox event counters.
type Denial ¶
type Denial struct {
Kind DenialKind `json:"kind"`
// Evidence is the output line the decision was based on (trimmed).
Evidence string `json:"evidence"`
// Path is the absolute path the failing operation referred to, when one
// could be extracted from the output.
Path string `json:"path,omitempty"`
// Op is OpWrite or OpRead when the output tells which one failed; empty
// when unknown or for network denials.
Op string `json:"op,omitempty"`
// WorkspaceRootEntry is true when the path is a new entry directly in the
// workspace root. The Landlock-only backend (no bubblewrap) grants a
// writable root that contains protected paths entry by entry, so creating
// new top-level files there fails although the workspace is writable.
WorkspaceRootEntry bool `json:"workspaceRootEntry,omitempty"`
}
Denial is the result of Classify: a failed command whose output looks like the sandbox, not the command itself, refused an operation.
func Classify ¶
Classify reports whether a failed command's output looks like a sandbox denial under policy p. Relative paths in the output are resolved against p.Workspace; use ClassifyAt when the command ran in another directory.
The caller must only classify runs that were really confined (policy covering the spawn site and an enforced backend): Classify cannot tell and would otherwise blame the sandbox for ordinary permission errors.
func ClassifyAt ¶
ClassifyAt is Classify resolving relative paths against cwd.
Heuristics (sandbox denials are only inferred, see the package doc):
- A zero exit code or a disabled policy is never a denial.
- Network errors ("Network is unreachable", "Could not resolve host", "Temporary failure in name resolution", "connect: operation not permitted", ...) count only while the policy restricts the network.
- File errors ("Permission denied", "Operation not permitted", "Read-only file system", EACCES/EPERM/EROFS, Seatbelt "deny(1) file-write") are cross-checked against the policy and the host's own access to the path mentioned: a path inside a writable root that is not protected, or one the host user cannot access either, is an ordinary permission problem, not the sandbox.
type DenialKind ¶
type DenialKind string
DenialKind is what a sandbox denial blocked.
const ( // DenialFS is a file-system access (write outside the writable roots, a // protected path, a denied path or, in strict mode, a read outside the // readable roots). DenialFS DenialKind = "fs" // DenialNet is a network access while the policy restricts the network. DenialNet DenialKind = "net" )
type EnvInherit ¶
type EnvInherit string
EnvInherit selects the base set of variables a sandboxed child inherits.
const ( // EnvInheritAll passes every variable except scrubbed ones (the default). EnvInheritAll EnvInherit = config.SandboxEnvInheritAll // EnvInheritCore passes only the core allowlist (PATH, HOME, TERM, ...) // plus EnvPolicy.Keep. EnvInheritCore EnvInherit = config.SandboxEnvInheritCore // EnvInheritNone passes only EnvPolicy.Keep. EnvInheritNone EnvInherit = config.SandboxEnvInheritNone )
type EnvPolicy ¶
type EnvPolicy struct {
Inherit EnvInherit `json:"inherit"`
// ScrubSecrets drops credential-looking variables (default true).
ScrubSecrets bool `json:"scrubSecrets"`
// Exclude are extra glob patterns (case-insensitive, '*' and '?') of
// variable names to drop. Exclude wins over everything.
Exclude []string `json:"exclude,omitempty"`
// Keep are glob patterns of variable names always passed through, even
// when they look like secrets or are outside the core set.
Keep []string `json:"keep,omitempty"`
}
EnvPolicy controls ScrubEnv.
type Event ¶
type Event struct {
Time time.Time `json:"time"`
Type EventType `json:"type"`
SessionID string `json:"sessionId,omitempty"`
Backend string `json:"backend,omitempty"`
Mode string `json:"mode,omitempty"`
// Kind/Op/Path describe a denial (see Denial); empty for other event
// types.
Kind string `json:"kind,omitempty"`
Op string `json:"op,omitempty"`
Path string `json:"path,omitempty"`
// Command is the offending or escalated command line. Callers pass the
// raw command; Emit redacts (internal/redact.String/Path) and truncates
// it before it reaches the ring buffer, the JSONL file, the log line or
// telemetry — a caller must never pre-redact it (that would double up
// escaping and could hide the "[REDACTED]" markers themselves).
Command string `json:"command,omitempty"`
// Reason explains an unavailable backend (Capability.Reason) or an
// escalation's justification.
Reason string `json:"reason,omitempty"`
// AutoAllowed is set on an EventEscalationRequested: whether the policy
// allows this escalation to be granted without an explicit approval.
AutoAllowed bool `json:"autoAllowed,omitempty"`
}
Event is one observability record: an applied/unavailable/denied/ escalation moment in the sandbox's life. It is kept for the in-memory ring buffer (RecentEvents), appended to the local JSONL log, logged through slog (so it shows on the TUI/WebUI logs page and — when opt-in remote telemetry is enabled — is forwarded to Better Stack the same way every other log record is, via internal/logging's tee handler), and counted for the pando_stats tool.
func RecentEvents ¶
func RecentEvents() []Event
RecentEvents returns a copy of the last (up to 200) sandbox events, oldest first. Safe for concurrent use; used by CLI/status/debug surfaces.
type EventType ¶
type EventType string
EventType names one of the observability events the sandbox emits. Every wrap site, and bash.go's denial/escalation handling, funnels through Emit with one of these (PANDO-US-0048).
const ( // EventApplied: a command was actually spawned confined — the policy // covered the purpose and the backend enforced it. Emitted once per // long-lived spawn (e.g. the persistent shell), not per command. EventApplied EventType = "sandbox.applied" // cannot enforce it here (unsupported OS, missing kernel feature, backend // not implemented yet, ...). Emitted at most once per process — see Emit. EventUnavailable EventType = "sandbox.unavailable" // EventDenied: a confined command failed in a way sandbox.Classify // attributes to the policy, not the command itself. EventDenied EventType = "sandbox.denied" // EventEscalationRequested is the bash tool's sandbox_permissions // "require_escalated" path asking the user (or auto-escalation) to run a // command once outside the sandbox. EventEscalationRequested EventType = "sandbox.escalation.requested" // EventEscalationGranted / EventEscalationDenied are the outcome of an // EventEscalationRequested. EventEscalationGranted EventType = "sandbox.escalation.granted" EventEscalationDenied EventType = "sandbox.escalation.denied" )
type Mode ¶
type Mode string
Mode is the sandbox profile.
const ( // ModeWorkspaceWrite (the default): write the workspace, temp dirs, // dependency caches and extra WritableRoots; read everything; network as // configured (allowed by default). ModeWorkspaceWrite Mode = config.SandboxModeWorkspaceWrite // ModeReadOnly: write temp dirs only; read everything; network restricted. ModeReadOnly Mode = config.SandboxModeReadOnly // ModeStrict: write the workspace, temp dirs and extra WritableRoots; read // only ReadableRoots (workspace, system dirs, toolchains); network // restricted. ModeStrict Mode = config.SandboxModeStrict // ModeOff: no confinement. ModeOff Mode = config.SandboxModeOff )
type Network ¶
type Network string
Network is the child network policy.
const ( // NetworkAllowed leaves networking untouched (the default). NetworkAllowed Network = config.SandboxNetworkAllowed // NetworkRestricted blocks outbound/inbound network for the child (the // backend decides the exact mechanism; AF_UNIX may stay allowed). NetworkRestricted Network = config.SandboxNetworkRestricted )
type Policy ¶
type Policy struct {
Mode Mode `json:"mode"`
Network Network `json:"network"`
// Workspace is the project directory the policy was resolved for.
Workspace string `json:"workspace"`
// WritableRoots may be written (recursively), except under
// ProtectedPaths and DenyPaths.
WritableRoots []string `json:"writableRoots,omitempty"`
// ReadableRoots, when non-empty (strict mode), is the complete set of
// readable directories. Empty means "read everything".
ReadableRoots []string `json:"readableRoots,omitempty"`
// ProtectedPaths stay read-only even inside WritableRoots: Pando's own
// config and data (anti self-disable) and git code-exec vectors.
ProtectedPaths []string `json:"protectedPaths,omitempty"`
// DenyPaths are denied for both read and write. Entries may be globs.
DenyPaths []string `json:"denyPaths,omitempty"`
// Env controls environment scrubbing.
Env EnvPolicy `json:"env"`
// AutoAllowBash is the configured intent to skip the bash permission
// prompt. It only applies while the sandbox is enforced; use the package
// AutoAllowBash() helper, which checks both.
AutoAllowBash bool `json:"autoAllowBash"`
// UseBwrap is the Linux bubblewrap preference.
UseBwrap BwrapPolicy `json:"useBwrap"`
// ExtendTo lists the extra spawn sites wrapped besides bash.
ExtendTo []Purpose `json:"extendTo,omitempty"`
// AllowAutoEscalation lets a denied command be re-run unsandboxed without
// an explicit approval.
AllowAutoEscalation bool `json:"allowAutoEscalation"`
// DenyConnectPorts are TCP ports a child must not connect to even though
// the network is allowed: the listeners of Pando itself (API, AG-UI, MCP
// HTTP, LLM proxy, IPC bus, ...) and of other live Pando processes, see
// GuardedPorts. Reaching one of them would let a command change the
// configuration or run code outside the sandbox. Filled by Resolve only
// when the network is allowed (a restricted network already refuses all
// TCP); part of Hash, so a new listener re-spawns the persistent shell.
DenyConnectPorts []int `json:"denyConnectPorts,omitempty"`
// Source is the layer that decided Mode. Informational; not hashed.
Source Source `json:"-"`
}
Policy is the resolved, platform-neutral sandbox policy. Paths are absolute and cleaned; lists are sorted and deduplicated. Roots may not exist on disk: backends must skip missing ones rather than fail.
func Current ¶
func Current() Policy
Current resolves the policy for the live configuration (config.Get()) and its working directory. It re-resolves on every call, so a settings change, an overlay reload or a PANDO_SANDBOX change is always reflected; it is cheap (no probes, a few stat calls to find the project config file).
func Resolve ¶
Resolve builds the effective policy from the loaded configuration and the workspace (defaulting to cfg.WorkingDir, then the process cwd). cfg may be nil, which resolves the defaults. The environment override and enterprise locks are applied; the project-only-tightens rule was already applied to cfg.Sandbox by config.Load.
func ResolveConfig ¶
func ResolveConfig(sc config.SandboxConfig, workspace string, opts ResolveOptions) Policy
ResolveConfig is Resolve with every input explicit; it touches neither the filesystem nor the process environment beyond what opts provides.
Mode precedence: lock > env > config > default. A locked sandbox.mode or sandbox.disabled makes PANDO_SANDBOX ignored for that aspect (an overlay that locks the sandbox on cannot be undone from the environment).
func (Policy) Enabled ¶
Enabled reports whether the policy asks for confinement at all. Whether the confinement is actually enforced depends on the Wrapper's Capability.
func (Policy) Hash ¶
Hash returns a stable hex digest of everything that affects enforcement (all fields except Source). Two policies with the same Hash confine a child identically, so a spawn site stores it and re-spawns when it changes.
func (Policy) RestrictsNetwork ¶
RestrictsNetwork reports whether the child's network must be blocked.
type Purpose ¶
type Purpose string
Purpose names a spawn site, for Policy.Covers.
const ( // PurposeBash is the bash tool's shell; it is always covered. PurposeBash Purpose = "bash" PurposeACPTerminals Purpose = config.SandboxExtendACPTerminals PurposeSkills Purpose = config.SandboxExtendSkills PurposeMCP Purpose = config.SandboxExtendMCP PurposeSubagents Purpose = config.SandboxExtendSubagents )
type ResolveOptions ¶
type ResolveOptions struct {
// GOOS selects the per-OS path tables (default runtime.GOOS).
GOOS string
// HomeDir is the user's home directory.
HomeDir string
// LookupEnv reads environment variables (PANDO_SANDBOX, TMPDIR, cache
// overrides, ...).
LookupEnv func(string) (string, bool)
// IsLocked reports whether a dotted config path (e.g. "sandbox.mode") is
// locked by an enterprise overlay. A locked path ignores PANDO_SANDBOX.
IsLocked func(path string) bool
// DataDir is config Data.Directory (protected; relative to the workspace).
DataDir string
// LocalConfigFile is the project config file found for the workspace,
// protected in addition to <ws>/.pando.{toml,json}.
LocalConfigFile string
// GuardedPorts are the TCP ports of Pando's own listeners (GuardedPorts),
// copied into Policy.DenyConnectPorts while the network is allowed.
GuardedPorts []int
}
ResolveOptions is the outside world ResolveConfig reads. Zero fields mean "nothing": Resolve fills them from the real process and configuration.
type SBPLOptions ¶
type SBPLOptions struct {
// Canonical returns the symlink-resolved form of an absolute path (for a
// missing path: its deepest existing ancestor resolved, plus the rest).
// nil means the identity.
Canonical func(path string) string
// Exists reports whether a path exists (without following a final
// symlink). It only gates the ancestor rename guards: a missing ancestor
// (e.g. <ws>/.git before `git init`) must stay creatable. nil means every
// path exists.
Exists func(path string) bool
}
SBPLOptions lets GenerateSBPLWith consult the filesystem. A nil function means "no filesystem": GenerateSBPL uses the zero value, which keeps the generator pure (golden tests pass fake paths).
func OSSBPLOptions ¶
func OSSBPLOptions() SBPLOptions
OSSBPLOptions returns SBPLOptions backed by the real filesystem, as used by the macOS wrapper.
type Source ¶
type Source string
Source says which layer decided the policy's mode (for status displays).
type Status ¶
type Status struct {
Policy Policy `json:"policy"`
Capability Capability `json:"capability"`
// Active: policy enabled and backend enforced.
Active bool `json:"active"`
// Hash is Policy.Hash().
Hash string `json:"hash"`
// Full: Active and without gaps (see Guarantees). Only then may bash
// skip its permission prompt.
Full bool `json:"full"`
// Gaps lists what an Active sandbox does not guarantee here (Guarantees).
Gaps []string `json:"gaps,omitempty"`
}
Status is the combined view a status badge or settings page needs.
func CurrentStatus ¶
func CurrentStatus() Status
CurrentStatus returns the Status for the live configuration.
func NewStatus ¶
func NewStatus(p Policy, c Capability) Status
NewStatus builds the Status of policy p under backend c.
type Wrapper ¶
type Wrapper interface {
// Capability reports the backend and whether it is enforced on this
// machine. It is cheap: probes run once and are cached.
Capability() Capability
// Wrap rewrites cmd (Path, Args, ExtraFiles, SysProcAttr, ...) so that,
// once started, it runs under p. It must be called before cmd.Start.
//
// Contract:
// - A disabled policy (p.Enabled() == false) leaves cmd untouched and
// returns nil.
// - When the backend is not enforced (Capability().Enforced == false)
// Wrap leaves cmd untouched and returns nil: the sandbox fails open,
// and callers surface that through Capability/Active.
// - An error means enforcement was available but could not be set up
// for this command (e.g. a path that cannot be encoded). cmd may be
// partially modified and must not be started; wrap such errors with
// ErrWrapFailed.
//
// Wrap does not touch cmd.Env; use ScrubEnv (or WrapCmd, which does both).
Wrap(cmd *exec.Cmd, p Policy) error
}
Wrapper confines a command to a Policy. Implementations are per OS (see the package doc) and must be safe for concurrent use.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package helper is the in-process half of the Linux sandbox: the `pando __sandbox-exec` re-exec helper.
|
Package helper is the in-process half of the Linux sandbox: the `pando __sandbox-exec` re-exec helper. |
|
Package portguard keeps the registry of TCP ports that belong to Pando itself: the HTTP API, the AG-UI listener, the MCP HTTP server, the LLM proxy, the IPC bus, OAuth callbacks, the browser's DevTools port, ...
|
Package portguard keeps the registry of TCP ports that belong to Pando itself: the HTTP API, the AG-UI listener, the MCP HTTP server, the LLM proxy, the IPC bus, OAuth callbacks, the browser's DevTools port, ... |