Documentation
¶
Overview ¶
Package sandbox provides a reusable, one-shot sandboxed command runner built on the same Docker client and hardened isolation preset (the isolation subpackage) that synchestra's long-lived runner uses.
Where the synchestra runner targets long-lived agent-in-container sessions that register back over gRPC, this package fills the complementary need: run a single command to completion inside a locked-down container, capture its logs and selected artifact files, then always tear the container down. It is intended for external modules — e.g. a coverage runner that executes `go test -coverprofile` inside a cloned repo and collects the produced profiles.
Security flags (read-only rootfs, CAP_DROP=ALL, non-root 65532, no-new-privileges, the daemon's default seccomp profile, init) are applied unconditionally via the shared isolation preset and cannot be weakened. The tunable knobs (CPU/memory/PID caps, network, timeout) live on Limits.
Index ¶
Constants ¶
const DefaultInputDir = "/workspace"
DefaultInputDir is where Job.InputTar is unpacked when Job.InputDir is empty. It is one of the preset's writable tmpfs paths.
const DefaultNetwork = "bridge"
DefaultNetwork is the network mode used when Job.Limits.Network is empty.
Tradeoff: a fully sealed sandbox would use "none", but the canonical workload for this package — `go test` against a cloned repo — needs network egress to reach the module proxy / GOPROXY and git. We therefore default to "bridge" (NAT egress, no inbound, no host network) rather than "none". The container is still hardened in every other dimension (read-only rootfs, dropped caps, non-root, seccomp). Callers that do not need egress should set Limits.Network = "none" explicitly.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Job ¶
type Job struct {
Image string // runner image carrying the toolchain
Cmd []string // command to run in the container
Env map[string]string // environment variables
WorkDir string // working directory inside the container
Mounts []Mount // trusted, read-only bind mounts into the container
// InputTar is an OPTIONAL tar archive of files/dirs handed to the workload as
// a writable source tree, with no path back to the host filesystem. This is
// the safe way to give untrusted code (e.g. `go test`) something to run on. A
// `git archive` tarball can be passed straight in.
//
// It is injected into an ephemeral named VOLUME mounted at InputDir, not a
// host bind mount: a trusted prep container creates the volume, chmods it so
// the non-root run user can write, and the tar is copied in while that prep
// container is running. The hardened main container then mounts the populated
// volume; the volume is removed when RunOnce returns. (Copying into the
// stopped main container instead would land on its rootfs and block the
// volume from mounting — hence the prep step.)
//
// The image must provide a POSIX shell with `chmod` and `sleep` (busybox,
// alpine, debian, golang, … all qualify). Tar entry paths are relative to
// InputDir (entry "main.go" -> <InputDir>/main.go). Set WorkDir to InputDir
// (or a subdir) to run Cmd there.
InputTar io.Reader
InputDir string // absolute dir the InputTar is unpacked into; defaults to "/workspace"
Collect []string // absolute in-container paths to copy out after exit
Limits Limits
}
Job is one sandboxed command to run to completion.
type Limits ¶
type Limits struct {
CPUs float64 // CPU quota, e.g. 2.0; 0 = unlimited
MemoryMB int64 // RAM cap in MiB; 0 = unlimited
PIDs int64 // max processes; 0 = unlimited
Timeout time.Duration // hard wall-clock kill; 0 = no timeout
Network string // "none" | "bridge" | named network; empty = DefaultNetwork
}
Limits are the tunable, non-security knobs of the hardened preset. Zero values mean "unset" (Docker default) except where noted.
type Mount ¶
Mount describes a bind mount from the host filesystem into the container. It mirrors the existing internal Mount type so callers and the rest of the codebase speak the same shape.
Mounts are intended for TRUSTED, read-only host data. Read-WRITE host bind mounts are discouraged for untrusted workloads (e.g. running arbitrary `go test` code): a malicious test could corrupt host files through the mount. To give untrusted code a writable source tree, inject it as a tar via Job.InputTar instead — the container then works on its own ephemeral copy on writable tmpfs, with no path back to the host filesystem.
type Result ¶
type Result struct {
ExitCode int // container exit status
Logs []byte // combined, de-multiplexed stdout+stderr
Artifacts map[string][]byte // Collect path -> file bytes (missing paths are omitted)
TimedOut bool // true if the container was killed at Limits.Timeout
}
Result is the outcome of a RunOnce call.
func RunOnce ¶
RunOnce pulls Image if it is not already present, creates a container with the hardened preset (tuned by Limits), runs Cmd to completion (or kills it at Limits.Timeout), captures combined stdout+stderr, copies out the Collect artifacts, and always removes the container before returning.
The returned Result is populated on a best-effort basis: a non-zero ExitCode or a timeout is reported through Result, not as an error. A non-nil error means the run could not be carried out (pull/create/start/wait failure); even then the container is force-removed.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package isolation is the single source of truth for the hardened container security preset ("profile B" in the generic-runners design spec): read-only rootfs, all Linux capabilities dropped, a non-root user, no-new-privileges, the default seccomp profile, an init process to reap zombies, and CPU / memory / PID resource caps.
|
Package isolation is the single source of truth for the hardened container security preset ("profile B" in the generic-runners design spec): read-only rootfs, all Linux capabilities dropped, a non-root user, no-new-privileges, the default seccomp profile, an init process to reap zombies, and CPU / memory / PID resource caps. |