Help us shape the future of Go! Take the 2026 Go Developer Survey

sandbox

package
v2.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package sandbox runs model-chosen shell commands under the operating system's sandbox (2.0 W4, #176): a seatbelt profile on macOS, bubblewrap on Linux. Writes are limited to the workspace, temp and cache directories; reads are not restricted; the network can be cut. Windows has no sandbox: commands run with the bash denylist alone.

Index

Constants

View Source
const (
	KindSeatbelt   = "seatbelt"
	KindBubblewrap = "bubblewrap"
)

The sandbox kinds Available reports, also named in blocked-write hints.

View Source
const DefaultEnabled = false

DefaultEnabled is whether the sandbox is on when no config says otherwise. Off for 2.0 (opt in with "sandbox": {"enabled": true} in ~/.celeste/config.json), so upgrading never breaks a build that writes outside the workspace.

Variables

This section is empty.

Functions

func Available

func Available() (kind string, ok bool)

Available reports which OS sandbox this process can use: "seatbelt" on macOS when sandbox-exec works, "bubblewrap" on Linux when bwrap is installed and its self-test passes; false elsewhere (Windows has none). Checked once per process.

func BwrapArgs

func BwrapArgs(p Policy, command string) []string

BwrapArgs is the bubblewrap argument list (everything after "bwrap") that runs command under p: the whole filesystem read-only, a fresh /dev and /proc, /run hidden behind a tmpfs (its sockets, the docker and session buses among them, reach outside the sandbox) except the directory /etc/resolv.conf points into (systemd's, NetworkManager's or resolvconf's), read-only, then each writable directory that exists bound read-write, then each read-only path that exists bound read-only over them. --unshare-pid is what lets an unprivileged bwrap mount /proc; --new-session takes the command off the terminal. bwrap itself leads the runner's new session and process group, so a timeout kills it whole.

func DefaultWritable

func DefaultWritable(home, workspace string) []string

DefaultWritable is the writable list for home and workspace: the workspace, the temp directories, the user cache directory and the build caches that exist (buildCaches). Resolved, de-duplicated and sorted.

func GitDirs

func GitDirs(workspace string) []string

GitDirs returns the git directories of the repository workspace is in (FindRepo), resolved and sorted: its git dir and, for a linked worktree, the repository's common dir (objects, refs). For a workspace at a plain repository's root they are inside it already; for a linked worktree (a subagent's isolated lane, or a user's own), a submodule or a subdirectory of a repository they are not, and without them git add and git commit fail under the sandbox. nil outside a repository and for a refused .git.

func GitPointers

func GitPointers(workspace string) []string

GitPointers returns the paths that tell git where workspace's git dirs are, which a sandboxed command must not change: each git dir's commondir and gitdir, the workspace repository's .git when it is a file, and workspace/.git when the repository's root is above the workspace (a .git there would be found first). They are read-only under the sandbox where they exist (ReadOnly); bubblewrap cannot bind over a missing one, so the runner also checks them after every sandboxed command (SnapshotPaths, RestorePaths). Resolved and sorted; nil outside a repository.

func GitProtected

func GitProtected(gitDirs []string) []string

GitProtected returns the paths in each of gitDirs that git outside the sandbox would run programs from, or would follow to another git dir, and that therefore stay read-only to sandboxed commands: config and config.worktree (core.fsmonitor, merge and filter drivers, core.hooksPath), hooks, and commondir and gitdir (git takes its config from the dir a commondir names, in any git dir, a plain .git included). A symlinked one is listed as the link and as its target, so neither can be replaced.

func Hint

func Hint(kind string, p Policy, output string) string

Hint explains a failed command that ran under the kind sandbox with policy p, when its output looks like the sandbox refused it: the sandbox and the config key to change (spec §5 W4, ruling 10). "" when the output names nothing the sandbox blocks.

func MakeGitProtected

func MakeGitProtected(gitDirs []string)

MakeGitProtected creates the GitProtected paths missing in each of gitDirs, as git would: an empty hooks directory and an empty config.worktree (read only when extensions.worktreeConfig is on), so bubblewrap, which can bind only over a path that exists, keeps them read-only too. config itself is never created. Errors are ignored: a path that cannot be created cannot be planted either.

func Normalize

func Normalize(paths []string) []string

Normalize resolves every path, then sorts and de-duplicates the list.

func Profile

func Profile(p Policy) string

Profile renders p as a seatbelt (SBPL) profile: everything is allowed but file writes, which are allowed only under the writable directories and to the terminal and pipe devices, never to the read-only paths; with the network off, everything but local unix sockets is denied too. Later rules win in SBPL, so each allowance follows its deny and the read-only paths' deny follows the allowance. That deny also covers each read-only path's ancestors themselves (not what is in them), so a directory holding one cannot be renamed aside and replaced.

func Resolve

func Resolve(path string) string

Resolve returns path made absolute, with symlinks resolved (the sandboxes compare real paths: /tmp is /private/tmp on macOS). For a path that does not exist yet, its deepest existing ancestor is resolved and the rest kept.

func RestorePaths

func RestorePaths(before []PathState) error

RestorePaths puts every path in before back as it was: what appeared is removed, a regular file that changed or went away is rewritten. Only a regular file or nothing can be restored; anything else that changed is reported. It returns an error naming the paths it found changed, nil when none was. Run outside the sandbox after a sandboxed command (shellrun), it undoes a pointer the sandbox could not bind read-only because it did not exist yet (GitPointers): every process of a bubblewrap sandbox is gone by then, so none can plant it again.

func SetAvailableForTest

func SetAvailableForTest(kind string, ok bool) func()

SetAvailableForTest makes Available report kind and ok (bubblewrap runs as "bwrap" from PATH) until the returned function restores the real check. For tests in other packages.

func Wrap

func Wrap(p Policy, command string) (argv []string, kind string)

Wrap returns the argv that runs command under p in the available sandbox, and that sandbox's kind. It returns nil and "" when p is disabled or no sandbox is available: the caller runs sh -c command itself.

Types

type PathState

type PathState struct {
	Path    string
	Present bool        // anything is there
	Regular bool        // a regular file, whose content is Data
	Data    []byte      // a regular file's content, a symlink's target
	Mode    os.FileMode // the type and permission bits
}

PathState is one watched path as SnapshotPaths saw it.

func SnapshotPaths

func SnapshotPaths(paths []string) []PathState

SnapshotPaths records each path: missing, a regular file and its content, a symlink and its target, or something else of a type.

type Policy

type Policy struct {
	Enabled   bool
	Workspace string   // resolved
	Writable  []string // resolved, de-duplicated, sorted; includes the defaults
	// ReadOnly stays read-only even inside a writable directory: each git
	// dir's config, hooks and commondir (GitProtected) and the paths that
	// lead git to them (GitPointers). Resolved.
	ReadOnly []string
	// Watch are the paths the runner puts back after each sandboxed
	// command (SnapshotPaths, RestorePaths): GitPointers, which bubblewrap
	// cannot bind read-only while they do not exist.
	Watch   []string
	Network bool
}

Policy is what one shell command may do.

type Repo

type Repo struct {
	WorkTree  string // the directory holding .git
	DotGit    string // that .git, a directory or a file
	GitDir    string // "" when the .git was refused
	CommonDir string // the GitDir itself unless it is a linked worktree's
	Refusal   string // why the .git was refused, and what to do; "" when it was not
}

Repo is the repository a directory is in, as FindRepo found it.

func FindRepo

func FindRepo(dir string) (r Repo, found bool)

FindRepo returns the repository dir is in, resolved: found is false when there is no .git in dir or above it. It reads the .git file or directory itself, as git does, rather than running git: the nearest ancestor's .git wins. The workspace's content controls that .git (a sandboxed command can write one), so nothing it names is believed unless git itself would have laid it out that way: a symlinked .git is refused; a .git file's "gitdir:" line must name a linked worktree's admin dir (<common>/worktrees/<name>, whose gitdir file points back to this .git and whose commondir is <common>, a real git dir) or a submodule's git dir (whose core.worktree points back to this directory). A refused .git gives a Repo with no GitDir, and Refusal says why. A plain .git directory is its own git dir and common dir; a commondir file in it is ignored.

Jump to

Keyboard shortcuts

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