Sandbox
sandbox
Write confinement of a subprocess: an OS sandbox wherever there is one.
A process that runs code it does not trust (a measured test suite, a replayed gate) must not write outside the directories it was given, and must not inherit the caller's environment. This module gives it both:
-
the sandbox - for the whole process tree, the platform's confinement:
-
macOS:
sandbox-execwith a profile that denies every file write except under the writable directories and to the character devices a process writes (/dev/null, ttys,/dev/fd), then denies again any write under the protected paths - the last matching rule wins, so a protected tree inside a writable area stays read-only (:func:darwin_profile); - Linux:
bwrap(bubblewrap), when installed: the root file system bound read-only,/devbound as is, each writable directory bound writable (:func:bubblewrap_argv).
Each confinement is probed with the very profile it will apply (a process
that is itself sandboxed often cannot nest one). When none works, the
isolation is detect-only (:data:UNCONFINED): the caller decides whether
an unconfined run is acceptable, and says so in what it reports;
- the environment - an allow-list (:data:
INHERITED_ENV): nothing else of the caller's environment reaches the process (noPYTHONPATH, noFORCE_COLOR, no credentials), and the home, temporary and XDG directories can be pointed into a writable directory (:func:private_environment).
:func:run_confined composes both for one command.
INHERITED_ENV = ('PATH', 'LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES', 'TZ', 'TERM', 'SYSTEMROOT', 'COMSPEC', 'PATHEXT', 'WINDIR')
module-attribute
The only variables a confined process inherits from the caller's environment.
PRIVATE_DIRS = {'HOME': 'home', 'USERPROFILE': 'home', 'TMPDIR': 'tmp', 'TEMP': 'tmp', 'TMP': 'tmp', 'XDG_CONFIG_HOME': 'config', 'XDG_CACHE_HOME': 'cache', 'XDG_DATA_HOME': 'data', 'XDG_STATE_HOME': 'state'}
module-attribute
Home, temporary and XDG variables -> their sub-directory of a private root.
SANDBOX_EXEC = '/usr/bin/sandbox-exec'
module-attribute
macOS's sandbox launcher.
UNCONFINED = Confinement(isolation='detect-only')
module-attribute
No sandbox: writes are not confined.
Isolation = Literal['sandbox', 'detect-only']
sandbox: writes confined by the OS; detect-only: no confinement.
ConfinedRun
dataclass
A finished confined process: its isolation and what it printed.
Source code in packages/axm/src/axm/sandbox.py
Confinement
dataclass
How a process is launched: the sandbox argv prefix, and what it guarantees.
Source code in packages/axm/src/axm/sandbox.py
prefix = ()
class-attribute
instance-attribute
Prepended to the command (empty when detect-only).
bubblewrap_argv(bwrap, writable, protected=())
The bwrap prefix: writable binds, then protected read-only binds.
Source code in packages/axm/src/axm/sandbox.py
confinement(writable, protected=())
The confinement writing only under writable, never under protected.
sandbox when the platform's sandbox applies here (probed from the first
writable directory), else :data:UNCONFINED (detect-only).
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Source code in packages/axm/src/axm/sandbox.py
darwin_profile(writable, protected=())
The sandbox-exec profile writing only under writable.
Reads are allowed; writes only under writable and to the writable
devices; any write under protected is denied even if it lies in a
writable area. Paths are resolved (/tmp is a symlink on macOS, the
kernel checks the real path).
Source code in packages/axm/src/axm/sandbox.py
inherited_environment(source=None)
The :data:INHERITED_ENV variables of source (the process's by default).
Source code in packages/axm/src/axm/sandbox.py
private_environment(root)
Home, temporary and XDG variables pointed into fresh directories of root.
The directories (:data:PRIVATE_DIRS) are created.
Source code in packages/axm/src/axm/sandbox.py
run_confined(command, *, writable, cwd, env=None, protected=(), timeout=None, confined=None)
Run command writing only under writable, in a filtered environment.
The environment is :func:inherited_environment updated with env;
nothing else of the caller's environment is passed. confined is the
confinement to apply, probed from writable and protected when
omitted: the caller refuses a detect-only one beforehand if it must.
Raises:
| Type | Description |
|---|---|
TimeoutExpired
|
The process outlived |
OSError
|
The process could not start. |