Documentation
¶
Overview ¶
Package sandbox is the wall of docs/SPEC-SANDBOX.md: one command run with its filesystem reach cut down by the operating system. This file is the platform-independent half — the Policy, the path resolution and refusal of rule 5, the per-platform root tables as data, and the temp directory of rule 8. The three bodies that apply a policy live behind build tags beside it, and on a platform whose backend is not built the body REFUSES (rule 1): there is no fallback, no degraded mode, and no partial wall.
Every platform whose body is not built yet REFUSES, and this file is that refusal. Rule 1 is "OS-enforced or refused": a stub that proceeded would be the silent sandbox the whole tool exists to prevent, so the linux (Landlock) and windows (AppContainer) bodies named in docs/SPEC-SANDBOX.md are not stubbed as pass-throughs — they are stubbed as NO, and the refusal names the platform so that a reader knows which body is missing rather than that "the sandbox failed".
Index ¶
- Constants
- func Ancestors(paths ...string) []string
- func Available() (string, bool)
- func Build(in Input) (*Policy, []Refusal)
- func ChildEnv(env []string, tmp string) []string
- func DarwinProfile(p *Policy) (text string, params []string, err error)
- func DroppedEnv(env []string) []string
- func Inside(path, dir string) bool
- func NetEnforceable() bool
- func Note() string
- func OptionalRoots(command string) []string
- func Run(p *Policy, env []string, stdin io.Reader, stdout, stderr io.Writer, ...) (int, error)
- type Input
- type Policy
- type Refusal
Constants ¶
const ( ExitRefused = 125 // nova-sandbox itself said NO before the command ran ExitNotExecuted = 126 // the command could not be executed and the tool was still there ExitNotFound = 127 // the command could not be resolved on the caller's PATH ExitProbeFailed = 1 // probe/check grammar: the verb ran and said NO ExitCannotRun = 2 // probe/check grammar: the verb could not run )
Exit codes. The exec path uses the env(1)/timeout(1) convention rather than SPEC.md's 0/1/2, because its status belongs to the wrapped command; the departure and its reason are in the spec's exit-codes section.
const ABI = "-"
ABI is the abi= field; there is no backend to have one.
const Backend = "none"
Backend is what the SANDBOX OK line would name here — and no line here says OK.
Variables ¶
This section is empty.
Functions ¶
func Ancestors ¶
Ancestors is every proper ancestor of the given paths, "/" excluded, sorted and unique. It is what the darwin profile's file-read-metadata literals are built from, and it is here rather than in the darwin body so that a test on any platform can assert its shape.
func Build ¶
Build turns an Input into a Policy, or into every independent refusal it holds. It creates exactly one directory, rule 8's, and only when the rest of the input is sound.
func ChildEnv ¶
ChildEnv is rule 9's environment: the caller's, unchanged, minus the three temp variables the tool sets to rule 8's directory — and minus the agent sockets. It is not a secrets tool: the credential the caller deliberately passed by environment must arrive, and every other variable passes through untouched.
The agent variables are the exception, and they are a fix this build made to the spec rather than something the spec asked for. SSH_AUTH_SOCK names a unix-domain socket that speaks for a private key without ever revealing it: a wall that denies ~/.ssh but leaves the agent reachable has not stopped the thing ~/.ssh was about. The profile denies unix sockets outside the write set, which is the wall; unsetting the variables is the fence beside it, so that an honest program does not try and a log does not have to be read to see that it could not.
func DarwinProfile ¶
DarwinProfile fills the template for ONE run and returns the profile text and the -D parameters that must accompany it. Caller paths never enter the text as data: they arrive as parameters and are read back as (param "READn"), (param "WRITEn") and (param "HOME"), so a directory name cannot rewrite the policy. The one place a path does enter the text is the ancestor literals, which is why Build refuses a path carrying an SBPL metacharacter.
Every param the filled profile names MUST be passed: an unfilled (param ...) is "invalid data type of path filter; expected pattern, got boolean" at exit 65, which is a broken run and not a weaker wall. The params returned here are exactly the params the returned text names.
func DroppedEnv ¶
DroppedEnv names the variables ChildEnv removes that are not the three temp ones, for the one NOTE line the tool prints before the command starts. A reader of a log should not have to diff two environments to learn that the agent was taken away.
func NetEnforceable ¶
func NetEnforceable() bool
NetEnforceable: an enforced network denial needs a backend first.
func OptionalRoots ¶
OptionalRoots is the machine's answer to the table above plus the directory of the resolved command, which is a root for exactly this run (the spec's roots table names it on all three platforms).
Types ¶
type Input ¶
type Input struct {
Reads []string
Writes []string
Cwd string // empty: the first --write (rule 13)
Tmp string // empty: <first --write>/.nova-sandbox-tmp (rule 8)
Name string // windows container name; accepted and ignored elsewhere
NetDeny bool
NetListen bool
Argv []string // the command and its arguments, everything after --
Home string // the caller's HOME as the child will see it (rule 9)
LookAt string // PATH to resolve the command on; empty means the process's own
}
Input is the argv as the caller typed it, before any resolution. Everything here is a claim about this job; Build turns it into a Policy or into refusals.
type Policy ¶
type Policy struct {
Reads []string // resolved, read-only, recursive
Writes []string // resolved, read+write, recursive; the first is load-bearing
OptRoots []string // the platform's optional roots that EXIST on this machine
Cwd string
Tmp string
Home string
Name string
NetDeny bool
NetListen bool
Command string // the resolved absolute path of the executable
Argv []string // Command followed by its arguments, verbatim
// Extra is the file descriptors the child gets ABOVE stdin/stdout/stderr, in order,
// starting at fd 3. It is never built from caller input: Build leaves it nil and the
// only writer is the probe, which hands its child one end of a pipe carrying the
// one-time value that makes the child the probe's own (cmd/nova-sandbox/main.go).
// A descriptor cannot be forged by a caller who merely knows an argument, which is
// why the probe's guard stands on one.
Extra []*os.File
}
Policy is one run's wall: resolved, absolute, existing paths and nothing guessed. The two named exceptions to "never guessed" are rule 4's, and both are recorded here as the caller's own first --write.
type Refusal ¶
type Refusal struct {
Reason string // the reason= token of the SANDBOX REFUSED line
Text string // what was wrong and what the flag wants, never a file's contents
}
Refusal is one independent problem, named by the flag it is about. Rule 16: a refusal says what the input wants and every independent problem is reported at once, so the callers below collect these rather than returning the first.
func ResolveCallerFile ¶
ResolveCallerFile is rule 5 for a caller path that names a FILE rather than a directory: --secret is the only one, and before this it was the one caller path the tool never resolved and never metacharacter-checked. It is absolute, it exists, it is not a directory, its symlinks are followed, and it carries nothing the generated policy cannot. A path that does not exist is a refusal, because a probe that "could not read" a file that was never there is a pass about nothing.