sandbox-command
sandbox-command is an asynchronous Rust Command like process launcher that utilizes bwrap (linux) or sandbox-exec (osx) for launching child processes in a controlled sandbox environment. Useful for running untrusted code, tools, or for use with agents.
Requirements
- A supported host operating system:
- Linux with
bwrapinstalled and usable by the current user. - macOS with
/usr/bin/sandbox-execavailable.
- Linux with
The platform sandbox executable is detected the first time SandboxCommand is initialized or spawned. The result is cached for the lifetime of the process.
Installing Bubblewrap on Linux
Install Bubblewrap using your distribution's package manager. For example:
# Debian or Ubuntu
# Fedora
# Arch
Bubblewrap also depends on the host's user-namespace configuration. If the executable is installed but spawning fails, check that unprivileged user namespaces are enabled and that the system permits Bubblewrap to create a sandbox.
Usage
Run a command and collect its output
SandboxCommand configures the child, and SandboxChild::wait_with_output collects both output streams asynchronously. Use Stdio::piped() for any stream that should be captured.
use Stdio;
use SandboxCommand;
async
initialize is optional. Calling it during application startup is useful when you want to report an unavailable sandbox before doing other work; spawn performs the same platform check if initialization was skipped.
Set the working directory and filesystem access
The working directory is canonicalized before spawning. It is writable by default. On Linux, bind_ro and bind_rw add paths at their existing locations inside the sandbox. On macOS, the working directory and paths passed to bind_rw are granted write access; the sandbox profile already permits reads required to execute the command.
use ;
use SandboxCommand;
async
Paths supplied to current_dir, bind_ro, and bind_rw should exist and be accessible to the parent process. The library does not create missing directories or copy files into the sandbox.
Stream output or interact with a child
spawn returns a SandboxChild instead of immediately waiting. Take the piped handles when you need to process output incrementally or send input to the child.
use Stdio;
use SandboxCommand;
use AsyncReadExt;
async
Other child-management operations include:
let process_id = child.id;
let status_if_finished = child.try_wait?;
child.start_kill?; // request termination without awaiting it
child.kill.await?; // terminate and await the kill operation
When using wait_with_output, the parent's stdin handle is closed automatically so a child waiting for EOF does not deadlock output collection.
Configure environment variables and standard I/O
The builder supports the usual environment operations. env_clear removes the inherited environment before applying later env or envs values. Standard streams use Tokio's Stdio configuration.
use Stdio;
let mut command = new;
command
.env_clear
.env
.envs
.env_remove
.stdin
.stdout
.stderr
.kill_on_drop;
API overview
SandboxCommand
| Method | Purpose |
|---|---|
new(program) |
Create a command for the executable path or name. |
arg(value), args(values) |
Add command-line arguments. |
env(key, value), envs(values) |
Set environment variables. |
env_remove(key), env_clear() |
Remove selected or all inherited environment variables. |
current_dir(path) |
Set and canonicalize the child's working directory. |
stdin(cfg), stdout(cfg), stderr(cfg) |
Configure standard I/O. |
kill_on_drop(kill) |
Pass Tokio's kill-on-drop setting to the child process. |
bind_ro(path) |
Add a read-only path binding on Linux. |
bind_rw(path) |
Add a writable path binding. |
spawn() |
Launch the sandboxed process. |
SandboxChild
SandboxChild exposes the child's optional stdin, stdout, and stderr handles and provides take_stdin, take_stdout, and take_stderr helpers. It also provides id, wait, try_wait, kill, start_kill, and wait_with_output.
The child owns any temporary macOS sandbox profile created for the process. The profile is removed after the child exits, fails to spawn, or is dropped.
Errors
Operations return SandboxCommandError for library-level failures:
| Variant | Meaning |
|---|---|
Initialization |
The host sandbox is unavailable or its configuration could not be prepared. |
Spawn |
The wrapped command could not be started. The original std::io::Error is preserved as the source. |
InvalidPath |
The working directory could not be canonicalized. |
Child operations such as wait and kill return Tokio's std::io::Result because they operate on an already-spawned process.
Sandbox behavior and limitations
The library is a process-launching wrapper, not a complete container runtime. Keep the host-specific behavior in mind when choosing it for a workload:
- Linux uses Bubblewrap and shares the host network with the child (
--share-net). It exposes/usr,/proc, and/devas needed for execution and makes the configured working directory available inside the sandbox. - macOS generates a temporary
sandbox-execprofile for each child. The profile allows network access, process execution/forking, and broad file reads while restricting writes to the working directory, temporary directories, and paths passed tobind_rw. - The library does not impose CPU, memory, wall-clock, or network quotas.
- The library does not validate whether a command is trusted, sanitize arguments, or automatically collect output.
- A command can still consume resources or produce large output. Applications should add timeouts, output limits, and resource controls appropriate to their threat model.
- Platform sandbox semantics and system policy remain authoritative. Test the exact host configuration used in production.
Troubleshooting
required sandbox executable ... was not found
Install the platform dependency and ensure it is discoverable:
- Linux: install
bubblewrapand make surebwrapis onPATH. - macOS: verify that
/usr/bin/sandbox-execexists.
sandbox_apply: Operation not permitted on macOS
The executable may be present while the host policy still prevents sandbox creation. This commonly occurs in restricted CI runners, containers, or other nested sandbox environments. Run the command on a host that permits sandbox-exec, or adjust the runner's security policy.
Bubblewrap starts but the child cannot run
Confirm that the working directory and any paths passed to bind_ro or bind_rw already exist. Also verify that the host permits unprivileged user namespaces and that the command's executable and runtime files are available through the sandbox's filesystem view.