Documentation
¶
Overview ¶
Package sandbox provides sandbox/bwrap: the single source of truth for the tenant sandbox view. The same view config drives two enforcement mechanisms:
- WrapArgv: subprocess isolation — translates the view into a bwrap argv prefix around bash / ACP subprocesses (each call runs in its own mount namespace, keeping 127.0.0.1 / the Pod network stack);
- CheckRead / CheckWrite: in-process enforcement — for the filesystem/sandbox decorator and other in-process tools that bwrap cannot confine.
View: tmpfs masks the tenant-root parent (neighbor tenants invisible) → bind back this tenant root (rw); global root read-only with secretFiles masked; minimal system ro-binds; no --unshare-net.
Index ¶
- Constants
- func New(cfg Config, d Deps) (capsandbox.Service, error)
- type Config
- type Deps
- type MapConfig
- type Sandbox
- func (s *Sandbox) CheckRead(ctx context.Context, path string) error
- func (s *Sandbox) CheckWrite(ctx context.Context, path string) error
- func (s *Sandbox) Enabled() bool
- func (s *Sandbox) TranslatePath(ctx context.Context, path string) (string, bool)
- func (s *Sandbox) WrapArgv(ctx context.Context, workDir string, inner []string) ([]string, error)
Constants ¶
const ( ModeOff = "off" // no wrapping, no checks — equivalent to no sandbox ModeAuto = "auto" // enable when bwrap is usable, warn and degrade otherwise (local dev only) ModeBwrap = "bwrap" // force enable; missing deps fail closed per FailIfUnavailable (default) )
Sandbox modes.
const ( MapModeRO = "ro" // default: read-only mapping MapModeRW = "rw" // read-write mapping (tenant can modify the host source) )
Map modes for MapConfig.Mode.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Config ¶
type Config struct {
Mode string `json:"mode"`
// Env marks the deployment environment: "prod"/"production" upgrades
// mode=auto to fail-closed bwrap, so a misconfig cannot silently run
// unsandboxed in production. Explicit config, no implicit env detection.
Env string `json:"env"`
// RoBinds are extra read-only binds (public read-only paths shared by all
// tenants; source and target are the same path). Host absolute paths or
// workspace refs (e.g. "global:share_dir", resolved per call per tenant).
RoBinds []string `json:"roBinds"`
// RwBinds are extra read-write binds (public writable paths shared by all
// tenants; beware concurrent write conflicts). Host absolute paths or
// workspace refs (missing dirs are created).
RwBinds []string `json:"rwBinds"`
// HidePaths are masked: directories become invisible and read-only,
// files are masked empty. Host absolute paths or workspace refs. Applied
// after all binds, so they can mask sensitive subpaths of system binds or
// the global root. Resolution fails closed (a failed mask means a leak).
HidePaths []string `json:"hidePaths"`
// SecretFiles are sensitive files under the global root masked empty
// (relative to the global root), default ["secrets.enc.json"].
// Applied after the global ro-bind and ro/rwBinds.
SecretFiles []string `json:"secretFiles"`
// HomeRef selects the in-sandbox HOME. Default "." (the writable tenant
// root, i.e. workspace "."). Values: a workspace ref ("global:dir",
// "local:path"), a host absolute path, or the literal "host" (pass the
// host user's home through, ro-bound — intended for single-tenant trusted
// setups). The resolved home must live inside the view (tenant root,
// global root or a configured bind); otherwise rendering fails closed
// instead of pointing HOME at an unmounted void.
HomeRef string `json:"homeRef"`
// Maps bind a host source onto a different in-sandbox destination, e.g.
// share the host's ~/.ssh or ~/.gitconfig into the tenant HOME. Applied
// after ro/rwBinds and before secretFiles/hidePaths (masks always win).
Maps []MapConfig `json:"maps"`
// SystemBinds overrides the default minimal system read-only bind list;
// empty uses the built-in default (/usr /bin /lib /lib64 /opt plus
// DNS/cert files under /etc).
SystemBinds []string `json:"systemBinds"`
// TmpBase is the base dir on a host tmpfs (default /dev/shm/shellbwrap):
// each tenant gets a subdir bound rw as in-sandbox /tmp. /dev/shm has its
// own size cap (usually half of RAM), preventing tmpfs writes from eating
// node memory. The explicit value "tmpfs" falls back to bwrap's own
// --tmpfs /tmp (no size limit, not recommended). Must be exclusive per
// runner instance on a shared host: startup removes leftover contents.
TmpBase string `json:"tmpBase"`
}
Config configures sandbox/bwrap: the tenant view fields (roBinds/rwBinds/ hidePaths/secretFiles — shared by subprocess wrapping and in-process enforcement) plus the bwrap-mechanism knobs (mode/systemBinds/tmpBase/ probe-failure policy).
func (*Config) SetDefaults ¶
func (c *Config) SetDefaults()
SetDefaults implements pluginkit.Defaulter.
type MapConfig ¶ added in v0.3.49
type MapConfig struct {
// Src is the host source: an absolute path or a workspace ref
// ("global:...", "local:..."), resolved per call per tenant.
Src string `json:"src"`
// Dst is the in-sandbox destination: an absolute path, or a relative path
// resolved against the rendered HOME (e.g. ".ssh" → $HOME/.ssh), so the
// mapping follows homeRef. A relative dst must not escape HOME ("..").
Dst string `json:"dst"`
// Mode is "ro" (default) or "rw". rw hands the host source's writable
// surface to the tenant — a deliberate choice (e.g. never rw-map .ssh).
Mode string `json:"mode"`
}
MapConfig binds a host source onto a different in-sandbox destination.
type Sandbox ¶
type Sandbox struct {
// contains filtered or unexported fields
}
Sandbox is the tenant sandbox view; tenant identity is resolved from ctx on every call.
func (*Sandbox) CheckRead ¶
CheckRead decides per the current tenant view whether an in-process read is allowed (used by the filesystem/sandbox decorator and other in-process tools — bwrap cannot confine Go code). path is a host absolute path. When the sandbox is disabled everything is allowed (confinement falls back to the tool layer, e.g. filesystem/local root).
The read model mirrors the bwrap view's allowlist: inside the sandbox only {tenantRoot, globalRoot, roBinds, rwBinds, systemBinds} exist and everything else is invisible; accordingly an in-process read is allowed only under {tenantRoot, globalRoot, roBinds, rwBinds} (system binds are a subprocess mechanism detail — fs tools have no legitimate need to read /usr; add an explicit roBind if one ever does). Deny rules:
- masked paths: matching hidePaths (incl. subpaths) or secretFiles;
- everything outside the allowlist (incl. neighbor tenants and any unrelated host path).
func (*Sandbox) CheckWrite ¶
CheckWrite decides per the current tenant view whether an in-process write is allowed. path is a host absolute path. Aligned with the bwrap view's read-only floor: only the tenant root and rwBinds are writable; everything else (global root, roBinds, system paths, unbound paths) is denied — the in-process equivalent of EROFS inside the sandbox.
func (*Sandbox) Enabled ¶
Enabled reports whether the sandbox is active (false in off mode or after an auto-mode probe fallback). When false, WrapArgv passes through and CheckRead/CheckWrite allow everything (confinement falls back to the tool layer).
func (*Sandbox) TranslatePath ¶ added in v0.3.49
TranslatePath implements capsandbox.PathTranslator: a path under a map dst is rewritten to its host src (longest dst prefix wins), so in-process consumers read/write the same backing content the subprocess sees at dst. The dst itself may not exist on the host — without this translation the in-process view would be an empty shell compared to the bwrap view.
func (*Sandbox) WrapArgv ¶
WrapArgv renders bwrap args for the current tenant and wraps the inner command (e.g. ["bash","-lc",cmd] or an ACP agent command). When the sandbox is disabled, inner is returned unchanged.
View: tmpfs masks the tenant-root parent (neighbor tenants invisible) → bind back this tenant root (rw); global root read-only with secretFiles masked; minimal system ro-binds; no --unshare-net (127.0.0.1 / Pod network stack preserved).