OS sandbox
The OS sandbox is the isolation envelope seal wraps around every command_run subprocess — bwrap on Linux, sandbox-exec on macOS. It’s one of two sandboxes in seal; the WASM sandbox wraps the agent itself, and this one wraps the commands the agent spawns.
Where the OS sandbox sits
Section titled “Where the OS sandbox sits”seal agent (WASM sandbox) │ ▼ WIT call into the hostseal daemon (native) │ ▼ capability check (manifest) │ ▼ OS sandbox (bwrap / sandbox-exec)your commandThe agent runs inside the WASM sandbox; everything it wants to do leaves the WASM envelope through a typed WIT call into the daemon. The daemon checks each call against the signed manifest — that’s the capability layer, the source of permission prompts. If the call is command_run, the spawned subprocess enters the OS sandbox before it can touch the kernel.
The capability layer tells you what the agent is asking for and gives you a chance to refuse. The OS sandbox is what enforces the answer at the kernel boundary — even if a command tried to read a file the manifest didn’t grant, the kernel would tell it the path doesn’t exist.
The OS sandbox is configured via the [sandbox.os] section of seal.toml.
There is no off-switch
Section titled “There is no off-switch”Every command_run invocation goes through the OS sandbox. Setting command_fs = "all" and command_tools = [] just removes the per-path filtering; it doesn’t remove the namespace isolation. The seal agent cannot run commands outside the sandbox under any circumstances.
If you need to run a command outside the sandbox, run it yourself.
What the OS sandbox blocks
Section titled “What the OS sandbox blocks”Filesystem
Section titled “Filesystem”The default command_fs = "system" baseline binds the standard system paths (/usr, /bin, /sbin, /lib, /lib64, /etc, /nix if present) read-only. Everything else is invisible unless the manifest names it:
[capabilities.allow.read.paths]adds read-only binds.[capabilities.allow.write.paths]adds read-write binds.[capabilities.allow.additional_directories]extends the project root with outside-the-project paths the manifest can reference.
$HOME is not bound by default in the system baseline. A command that tries to read ~/.cargo/config.toml without a matching grant gets ENOENT — the path isn’t there, from the command’s point of view.
Three other baselines tune this:
"none"— nothing bound by default. The strictest posture: every path the agent’s commands need has to be allowlisted explicitly, either through grants under[capabilities.allow.read]/[capabilities.allow.write]or through curated tool bundles. Tools that depend on/usr/bin,/bin, or library paths will fail to start unless you add those paths yourself or list a bundle that supplies them. Bundles work the same way they do under any other baseline —command_tools = ["cargo"]still contributes its read binds, env-var passthroughs, and network allowlist on top of anonebaseline."permissive"—systemplus$HOMEread-only."all"— entire filesystem read-only.
Network
Section titled “Network”Outbound traffic from a sandboxed command is routed through a per-session MITM proxy with a session-issued CA. The proxy enforces the host allowlist:
[capabilities.allow.commands] patternsdeclares which command patterns can run AND which domains they can reach. Bare-string entries ("cargo:*") inherit the section’sdefault_domains; inline-table entries ({ command, domains }) union with the default unlessinherit_default_domains = falseopts out.- Domains can be literal (
api.github.com) or*.<registrable>wildcards (*.github.com). - Curated tool bundles (see below) pre-populate the most common allowlists —
git/gh/jjgetgithub.comby default;cargogetscrates.io;npm/bun/yarn/pnpmget the npm registry.
Traffic to disallowed hosts fails with a connection-refused error from inside the sandbox. The proxy logs every denied attempt for the session audit log.
Sensitive files
Section titled “Sensitive files”Even when a filesystem baseline would otherwise expose them, the standard sensitive-file masklist hides specific paths when command_mask_secrets = true (the default):
/etc/shadow,/etc/sudoers, SSH host keys.~/.ssh/*,~/.aws/credentials,~/.gnupg/*,~/.config/op/.- Cloud-provider credential dirs, browser cookie stores.
A command attempting to read one of these sees ENOENT — the same response as a non-existent file — regardless of which baseline is active.
Process and IPC
Section titled “Process and IPC”A sandboxed subprocess can’t ptrace, can’t share IPC namespaces with non-sandboxed processes, and runs in its own PID + network namespace on Linux. It can fork children, but those children inherit the same envelope.
Curated tool bundles
Section titled “Curated tool bundles”Most common dev tools have config dirs, env vars, and registries they need to function. Listing each requirement by hand is error-prone, so Seal ships bundles — pre-baked configurations for each tool that you opt into with one entry:
[sandbox.os]command_tools = ["git", "gh", "cargo", "bun"]Each bundle contributes:
- Read binds for the tool’s config dirs (e.g.
~/.gitconfig,~/.cargo/config.toml). - Env-var passthroughs for the variables the tool reads (e.g.
GITHUB_TOKEN,CARGO_*). - Default network allowlist for the tool’s canonical hosts (e.g.
github.comfor git,crates.iofor cargo). - Optionally, write binds for caches and install dirs (cargo’s registry cache, npm’s install cache, …).
Each bundle has knobs you can tune via the expanded form:
[sandbox.os]command_tools = [ "git", { tool = "cargo", fetch = false, install = true }, { tool = "node", domains = ["api.openai.com"], default_domains = true },]The full per-bundle reference is at sandbox.os.command_tools.
Wrappers
Section titled “Wrappers”Build orchestrators (just, make, mise) commonly spawn the real tool as a child inside the same sandbox namespace. Without a hint, just ci (which internally runs cargo test) wouldn’t pick up the cargo bundle’s network allowlist — the matched_pattern is just ci:*, not cargo build:*.
The wrappers knob fixes this:
[sandbox.os]command_tools = ["cargo"]wrappers = ["just", "make"]Now the cargo bundle applies whenever the parent command pattern starts with just or make. Per-bundle overrides work too: { tool = "cargo", wrappers = ["just"] } only widens the cargo bundle, not the others.
Audit log
Section titled “Audit log”Every sandbox-enforced denial — filesystem, network, or masked secret — lands in the per-session audit log at ~/.seal/audit/<session-id>.jsonl. Each entry carries the command pattern that triggered the denial, the path or host that was blocked, and the timestamp.
Useful for after-the-fact “wait, what did the agent try to do?” review, and for tuning the manifest: if you see the same denial in the log five times in a row, that’s a signal to either add the grant explicitly or refuse the workflow.
Composing with the capability layer
Section titled “Composing with the capability layer”The two layers don’t duplicate each other — they enforce different shapes:
- The capability layer asks the user before letting the agent do something. Its job is to surface intent.
- The OS sandbox enforces the answer at the kernel boundary. Its job is to make refusal stick.
A grant in [capabilities.allow.commands] makes the daemon let the call through; that’s necessary but not sufficient. The OS sandbox still has to be able to see the relevant files and reach the relevant hosts. If your manifest grants cargo build:* but doesn’t bind ~/.cargo/registry, the build fails inside the sandbox with a “couldn’t fetch dependencies” error — the capability check passed, but the OS layer had nothing for cargo to read.
This is where the curated bundles earn their keep: instead of you naming ~/.cargo/registry, ~/.cargo/config.toml, every CARGO_* env var, and the crates.io domain by hand, command_tools = ["cargo"] supplies the whole bundle of OS-side bindings. You still add the command pattern (cargo:* under [capabilities.allow.commands], or via a permission prompt) — the bundle covers the filesystem and network surface that pattern’s command will need.
See also
Section titled “See also”- Manifest reference:
[sandbox.os]— every knob. - Manifest reference:
[sandbox.os.command_tools]— every curated bundle. - Troubleshooting — common sandbox-related error messages and how to read them.