The sandbox boundary
firma run wraps an agent in a sandbox, routes its outbound traffic through the Sidecar, and — on structural backends — removes the agent’s ability to bypass that route. On default macOS and WSL2 paths, enforcement is proxy-based: traffic is mediated only for cooperative HTTP clients that honor HTTP_PROXY. macOS also has experimental structural paths behind explicit environment gates. This distinction is the most important thing to understand before relying on firma run for security enforcement.
The problem with proxy env vars alone
Section titled “The problem with proxy env vars alone”HTTP_PROXY is a hint, not a constraint. It works because most HTTP libraries respect it by convention. But:
- A library that doesn’t respect proxy env vars (some Go binaries, some C libraries) bypasses it silently.
- An agent that opens raw TCP sockets bypasses it.
- An agent that spawns a child process with a clean environment bypasses it.
- Anything that reads
/etc/hosts, makes its own DNS query, or talks UDP bypasses it.
For a cooperative agent on a developer laptop, none of this matters. For a less-trusted agent — anything you didn’t write, anything running prompts you don’t fully control, anything that could be compromised — the proxy hint is not a security boundary. It’s a convention, and the agent can choose to ignore it.
Structural vs proxy-only enforcement
Section titled “Structural vs proxy-only enforcement”firma run has two materially different enforcement modes:
Structural confinement (Linux bwrap; experimental macOS structural modes): The sandbox removes the agent’s ability to bypass the proxy at the OS level. In the Linux path, the agent runs in a network namespace where the only reachable destination is the proxy bridge — raw sockets, DNS lookups, and child processes all dead-end inside the sandbox. The macOS sandbox-exec experiment blocks external IP egress but remains loopback-scoped. The macOS VZ guest path emits a strict launch contract for an operator-provided Virtualization.framework runner, which must boot the guest with bridge-only egress. No extra cooperation from the agent is required once the structural boundary is active.
Proxy-only / compatibility mode (macOS vz, Windows/WSL2 wsl2): The agent runs in the host environment with HTTP_PROXY and related environment variables injected. Outbound mediation depends on the agent (or its HTTP library) respecting those variables. Raw sockets, proxy-env-unset children, and non-HTTP protocols can bypass the Sidecar.
This is not a preference — it is a current capability gap. Cross-platform structural parity is tracked as a separate implementation effort. The macOS strategy decision is documented in macOS structural confinement strategy: VZ guest-based structural parity is the primary path, while ESF-native controls are treated as targeted hardening rather than a standalone structural network boundary.
To prevent false confidence, firma run fails closed when a non-structural backend is selected. You must explicitly opt in with --allow-non-structural (or set run.allow_non_structural = true in firma.toml) to acknowledge the limitations of proxy-only mode:
# macOS: must opt into proxy-only compatibility modefirma run --profile generic --allow-non-structural -- curl https://example.com
# Or persist the opt-in in firma.toml:# [run]# allow_non_structural = trueWithout the opt-in, firma run prints a typed error explaining that the selected backend provides proxy-only enforcement, and how to proceed.
What firma run does
Section titled “What firma run does”firma run wraps the agent’s launch in a sandbox. The specific mechanisms depend on the backend:
Structural backends
Section titled “Structural backends”- Network boundary. On Linux
bwrap, the agent runs in a network namespace where the only reachable network destination is a host-side process listening on127.0.0.1:18080(the proxy bridge). On experimental macOS network-deny mode, the agent is restricted to loopback and external IP egress is blocked. On macOS VZ guest mode,firma runwrites a launch contract that requires the configured guest runner to expose only the sidecar bridge and DNS stub to the guest. - Proxy bridge. A small helper inside the sandbox listens on
127.0.0.1:18080and forwards bytes over a Unix socket to the host’s Sidecar (typically at$XDG_RUNTIME_DIR/firma/sidecar.sock). On Linuxbwrap, the agent’s traffic has nowhere else to go. On experimental macOS network-deny mode, external IP egress is blocked but other loopback services remain a caveat. On macOS VZ guest mode, the bridge URL is part of the runner contract. - DNS stub. A stub resolver answers DNS queries deterministically. On Linux it runs inside the sandbox path. On macOS structural paths,
firma runstarts a host-side refusal stub; sandbox-exec reaches it on loopback, and the VZ guest runner must wire guest DNS to it. Random outbound DNS must not be a successful bypass. HTTP_PROXYinjection. For agents that do respect proxy env vars,firma runsetsHTTP_PROXY=http://127.0.0.1:18080so they don’t need any code change.- Identity remap. The agent runs under a sandbox user (configurable via
--identity-mode), so it can’t read host secrets via filesystem.
The result is that an agent running under Linux structural firma run can attempt to bypass the proxy in any way it likes — open raw sockets, set its own DNS, fork a child process — and every one of those attempts dead-ends inside the sandbox. Experimental macOS network-deny mode provides a narrower claim: external IP egress is blocked, while host loopback remains a known limit. Experimental macOS VZ guest mode has the stronger structural target, but depends on a runner and guest image bundle and still needs hardware E2E evidence before it becomes the default claim.
Proxy-only backends (vz, wsl2)
Section titled “Proxy-only backends (vz, wsl2)”HTTP_PROXYinjection.firma runsetsHTTP_PROXY,HTTPS_PROXY, and related environment variables pointing to the Sidecar. Agent processes that read these vars will route traffic through the Sidecar automatically.NO_PROXYclearing. Built-in profiles clearNO_PROXY/no_proxyto prevent a host environment variable from silently routing traffic around the Sidecar.- Sandbox-exec (macOS only). On macOS,
sandbox-execprovides filesystem-level confinement for theclaude-codeprofile (blocking reads/writes to~/.ssh,~/.aws, etc.), but it does not provide mandatory network confinement. - Require explicit opt-in.
firma runfails closed unless--allow-non-structuralis passed, making the proxy-only status explicit and noisy.
For well-behaved agents — standard HTTP libraries, no raw socket use — proxy-only enforcement is effective in practice. For adversarial or minimally cooperative agents, only the structural backends provide a hard guarantee.
The four backends
Section titled “The four backends”Network sandboxing primitives differ across OSes, so firma run selects a backend by platform:
| Backend | Platform | Mechanism | Enforcement |
|---|---|---|---|
bwrap | Linux | Unprivileged user namespaces (bubblewrap) | Structural |
firecracker | Linux | KVM micro-VM | Planned |
vz | macOS | Host proxy bridge + HTTP proxy injection (HTTP_PROXY); optional sandbox-exec network-deny or VZ guest runner contract | Proxy-only (default); experimental structural modes via FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1 or FIRMA_RUN_VZ_GUEST=1 |
wsl2 | Windows | HTTP proxy injection (HTTP_PROXY) | Proxy-only |
You can override the platform default with --backend:
firma run --profile generic --backend firecracker -- python my_agent.pyThe choice is mostly an operational one: bwrap is fast to start and lightweight; current vz and wsl2 are compatibility options on their platforms; the Firecracker backend is planned as a VM path.
firma run cannot escalate privileges. On Linux it requires unprivileged user namespaces (which most modern distros enable by default). On WSL hosts, implicit backend selection uses the wsl2 compatibility backend instead of attempting bwrap.
Cross-OS capability matrix
Section titled “Cross-OS capability matrix”Use this section as the current release claim for firma run confinement. It tracks runtime invariants, not only backend names, so a backend can be useful while still missing a structural guarantee.
| OS | Modes and release stance |
|---|---|
| Linux | bwrap: current structural release path.firecracker: planned backend; no current release claim. |
| macOS | vz default: proxy-only compatibility path.vz + FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1: experimental sandbox-exec network-deny mode; not a default release claim.vz + FIRMA_RUN_VZ_GUEST=1: experimental VZ guest runner contract mode; not a default release claim. |
| Windows / WSL2 | wsl2: proxy-only compatibility path. |
Available modes:
bwrap- current Linux default.firecracker- planned Linux micro-VM backend.
| Runtime invariant | bwrap | firecracker |
|---|---|---|
| Sidecar-only egress | Yes. Network namespace makes the proxy bridge the only useful exit. | Planned. KVM micro-VM network isolation target. |
| DNS confinement | Yes. Sandbox resolver points at the local DNS stub or fails closed. | Planned. Guest-local deterministic resolver target. |
| Fail-closed startup | Yes. Backend, sidecar, policy, and seccomp setup failures block launch. | Planned. |
| Fail-closed runtime | Yes. With no direct egress route, sidecar or bridge loss breaks outbound traffic. | Planned. |
| Child/process-tree bypass resistance | Yes. Child processes inherit the network namespace. | Planned. |
| Syscall/seccomp enforcement | Linux-only. Static seccomp cBPF is supported with a bounded Cedar-subset projection. | Planned. Linux guest path should reuse static kernel controls where applicable. |
| Immutable execution envelope | Yes. Runtime fixes identity, env, mounts, routing, and optional seccomp before launch. | Planned. |
| Interactive CLI/TUI support | Yes. Stdio, signals, and exit status are preserved through the wrapper path. | Planned. |
| Evidence status | Runtime code, Linux E2E harness, and FIR-111 seccomp spike artifacts exist. | Planned backend; no release evidence. |
Available modes:
vzdefault - proxy-only host compatibility mode.vz+FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1- experimentalsandbox-execnetwork-deny mode.vz+FIRMA_RUN_VZ_GUEST=1- experimental VZ guest runner contract mode.
vz default
Section titled “vz default”| Runtime invariant | Status |
|---|---|
| Sidecar-only egress | No. Cooperative HTTP clients are mediated through injected proxy env. |
| DNS confinement | No. Host DNS remains available to non-cooperative processes. |
| Fail-closed startup | Yes for startup. Launch is blocked unless proxy-only mode is explicitly accepted and the sidecar path is prepared. |
| Fail-closed runtime | Partial. Proxy-routed clients fail, but direct sockets can bypass. |
| Child/process-tree bypass resistance | No. Children can ignore or clear proxy env. |
| Syscall/seccomp enforcement | No. seccomp is unavailable on macOS. |
| Immutable execution envelope | Partial. Runtime creates a deterministic launch envelope, but proxy-only children can still bypass network intent. |
| Interactive CLI/TUI support | Yes. Host-process compatibility path preserves normal CLI behavior. |
| Evidence status | Runtime proof logs and unit tests support the proxy-only claim. |
vz + FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1
Section titled “vz + FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1”| Runtime invariant | Status |
|---|---|
| Sidecar-only egress | Partial / experimental. External IP egress is denied by sandbox-exec, but all loopback remains reachable. |
| DNS confinement | Partial / experimental. Non-loopback DNS is blocked by network denial; the host resolver is not replaced. |
| Fail-closed startup | Yes / experimental. Network-deny mode still uses the same startup fail-closed path. |
| Fail-closed runtime | Partial / experimental. External egress remains denied, but loopback-all scope is a residual caveat. |
| Child/process-tree bypass resistance | Partial / experimental. Child processes should inherit the MAC sandbox label for external egress denial; loopback remains reachable and hardware E2E is pending. |
| Syscall/seccomp enforcement | No seccomp. Uses TrustedBSD MAC network rules, not syscall filtering. |
| Immutable execution envelope | Partial / experimental. Launch envelope plus MAC profile are fixed before process start; residual loopback caveat remains. |
| Interactive CLI/TUI support | Yes / experimental. Still host-process based through sandbox-exec. |
| Evidence status | Runtime code and unit tests exist; macOS hardware E2E assertions are written but not yet green evidence. |
vz + FIRMA_RUN_VZ_GUEST=1
Section titled “vz + FIRMA_RUN_VZ_GUEST=1”| Runtime invariant | Status |
|---|---|
| Sidecar-only egress | Target / experimental. Launch contract requires bridge-only guest egress; the runner and guest image must enforce it. |
| DNS confinement | Target / experimental. Contract carries the DNS stub address; the runner must wire guest DNS to it. |
| Fail-closed startup | Target / experimental. Artifact validation and contract generation fail closed; runner-side preflight must also prove the guest boundary. |
| Fail-closed runtime | Target / experimental. Guest route and bridge loss behavior must be proven by the runner and hardware E2E tests. |
| Child/process-tree bypass resistance | Target / experimental. Guest boundary should cover the process tree; runner and guest proof are pending. |
| Syscall/seccomp enforcement | Target only. Contract can carry a seccomp filter path for an in-guest Linux runner; guest loading is not a current release claim. |
| Immutable execution envelope | Target / experimental. Versioned launch contract records command, env, mounts, network endpoints, and required invariants. |
| Interactive CLI/TUI support | Target / experimental. Runner must preserve stdio, signals, exit status, terminal resize, and TTY behavior. |
| Evidence status | Runtime contract code and unit tests exist; signed VZ runner, guest image lifecycle, route proof, and hardware E2E are still pending. |
Windows / WSL2
Section titled “Windows / WSL2”Available mode:
wsl2- proxy-only compatibility mode.
| Runtime invariant | wsl2 |
|---|---|
| Sidecar-only egress | No. Cooperative proxy env only. |
| DNS confinement | No. No mandatory DNS boundary. |
| Fail-closed startup | Yes for startup. Launch is blocked unless proxy-only mode is explicitly accepted and the sidecar path is prepared. |
| Fail-closed runtime | Partial. Proxy-routed clients fail, but direct sockets can bypass. |
| Child/process-tree bypass resistance | No. Children can ignore or clear proxy env. |
| Syscall/seccomp enforcement | No current claim. |
| Immutable execution envelope | Partial. Runtime injects env and identity into the WSL launch, but no structural boundary backs it. |
| Interactive CLI/TUI support | Partial. Basic process execution is supported; WSL terminal behavior depends on host setup. |
| Evidence status | Runtime code and unit tests support the proxy-only claim. |
Known limits
Section titled “Known limits”These limits are part of the current release posture. Treat them as constraints on what firma run can claim today, not as implementation bugs.
| Limit | Current status | Practical effect |
|---|---|---|
macOS default vz is proxy-only | The default macOS backend launches a host process with proxy environment injection and a host-side bridge. | Cooperative HTTP clients are mediated, but raw sockets, direct DNS, and proxy-env-unset children can bypass the Sidecar. |
macOS sandbox-exec mode is loopback-scoped | FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1 blocks external IP egress, but allows loopback so the proxy bridge and DNS stub can work. | Other host services listening on loopback remain reachable; this is not Linux-equivalent network namespace confinement. |
| VZ guest mode is not a default release claim | FIRMA_RUN_VZ_GUEST=1 validates runner and image paths, writes the launch contract, and requires a configured runner and guest bundle to enforce bridge-only egress. | It remains experimental until hardware E2E proves guest lifecycle, guest routing, DNS confinement, and runtime loss behavior. |
| macOS hardware E2E evidence is not complete | The macOS assertion schema exists, but the structural suite is not yet green on supported macOS hardware. | macOS has no current release claim for Linux-style egress, DNS, child-process, or runtime fail-closed guarantees. |
| WSL2 is proxy-only | The current wsl2 backend injects proxy env into the launched command. | It is useful for compatibility, but it is not a mandatory network boundary for non-cooperative processes. |
| seccomp is Linux-only static cBPF today | Managed seccomp is available for the Linux bwrap path as a static filter with a bounded Cedar-subset projection. | macOS and WSL2 do not get seccomp enforcement, and Linux syscall filtering is not live Cedar policy evaluation. |
Structural means the sandbox removes the agent’s ability to bypass the proxy at the OS level - no extra cooperation from the agent is required. The experimental macOS network-deny mode is narrower than Linux because it is loopback-scoped rather than bridge-port-scoped. The experimental macOS VZ guest mode has the stronger structural target, but the runner and guest image own the actual Virtualization.framework lifecycle and in-guest enforcement.
Proxy-only means enforcement depends on the agent or its HTTP library respecting HTTP_PROXY. firma run refuses to launch proxy-only backends by default; --allow-non-structural is an explicit opt-in to that weaker enforcement model.
NO_PROXY / no_proxy are cleared in all built-in profiles to prevent a host-env override from silently routing traffic around the proxy Sidecar on macOS and WSL2.
When Firma run logs “backend compatibility proof”
Section titled “When Firma run logs “backend compatibility proof””When firma run runs with a structural backend, it logs:
structural=true ... "backend network enforcement proof"When running with a proxy-only backend (after --allow-non-structural opt-in), it logs a warning instead:
structural=false mode=proxy_only enforced=false ... "backend compatibility proof — proxy-only mode; agent egress is NOT mandatorily confined"This distinction exists so that log scanners and monitoring tools cannot misinterpret proxy-only mode as mandatory network confinement.
Profiles
Section titled “Profiles”A profile declares the runtime shape: env injection, sandbox identity, network policy, capability lease behavior. The shipped profiles are:
generic— works for any agent. Sandboxed, proxy-routed,HTTP_PROXYset. The default.codex— tuned for code-generation agents (Claude Code, Codex, Cursor) that need filesystem access to a project directory. Allows mounting a workspace path.claude-code— tuned for Claude Code with home-directory path masking andsandbox-execconfinement on macOS.
Custom profiles live in TOML and you can pass them via --config. For most workloads, generic is correct and you should reach for a custom profile only when you’ve hit a limit.
The profile resolves at startup, before the sandbox is built. You can preview it without launching the agent:
firma run --profile generic --print-effective-config -- echo hiThis prints the resolved config as JSON so you can see exactly what mounts, env vars, and identity remaps will be applied.
Capability handling
Section titled “Capability handling”The preferred runtime shape keeps capability material outside the agent process:
- Before the sandbox starts, the operator stages capability material for the Sidecar, normally through
[capability_seed]. - The host-side Sidecar reads that seed outside the sandbox.
- Inside the sandbox, the agent only needs
HTTP_PROXY=http://127.0.0.1:18080. - When the agent makes an outbound call, the Sidecar selects the right capability based on
(session_id, action_class, resource)— which it knows from the request, not from the agent.
That is the mode to use when token non-exposure is a security requirement. Current firma run --capability-file support is a compatibility path: the runtime reads the file and exports FIRMA_CAPABILITY_TOKEN / FIRMA_CAPABILITY_FILE into the wrapped process environment. Do not rely on token non-exposure in that mode. The long-term direction is to keep the agent’s only superpower as “ask the Sidecar to do this thing”; the Sidecar decides whether the capability covers it.
What the sandbox protects against
Section titled “What the sandbox protects against”What firma run protects against depends on the enforcement mode. The lists below reflect this: Linux structural mode provides the strongest guarantee, experimental macOS network-deny mode blocks external IP egress with loopback caveats, and proxy-only backends provide best-effort mediation.
Structural backends protect against:
Section titled “Structural backends protect against:”- An agent that intentionally tries to bypass the proxy with raw TCP / UDP / non-HTTP protocols.
- An agent that spawns child processes that don’t inherit
HTTP_PROXY. - DNS exfiltration via crafted lookups.
- Filesystem-mediated leaks across user boundaries (via identity remap).
- An agent reading host environment variables it shouldn’t see.
Proxy-only backends (vz, wsl2) protect against:
Section titled “Proxy-only backends (vz, wsl2) protect against:”- A cooperative agent that routes its HTTP/HTTPS traffic through
HTTP_PROXY/HTTPS_PROXY. - A host environment where
NO_PROXYmight accidentally route traffic around the Sidecar (cleared by built-in profiles).
Proxy-only backends do not protect against:
Section titled “Proxy-only backends do not protect against:”- A non-cooperative agent that opens raw sockets, ignores
HTTP_PROXY, or uses non-HTTP protocols. - An agent that spawns children with a clean environment that doesn’t inherit proxy variables.
- DNS exfiltration via direct UDP queries.
No backend protects against:
Section titled “No backend protects against:”- Bugs in the chosen backend (bwrap escapes, VZ guest-host vulnerabilities, etc.). The sandbox is as strong as the backend.
- An agent that targets the Sidecar itself (e.g. exhausts its connections, exploits a parsing bug). The Sidecar is your TCB; treat it that way.
- Side channels (timing, power consumption, etc.). OpenFirma is a network policy boundary, not a side-channel boundary.
- Cooperative protocol abuse (e.g. an agent that uses an allowed destination to smuggle data). That’s a policy problem, not a sandbox problem — see Threat model & bypasses.
- Anything that happens inside the sandbox that doesn’t generate network traffic (an agent that just churns CPU, or that writes to its own scratch space).
The sandbox is the plumbing that routes outbound calls through the Sidecar. On structural backends, that plumbing is mandatory; on proxy-only backends, it depends on agent cooperation. It is the policy and capability layers, not the sandbox, that decide whether a given call is OK.
When to use firma run vs proxy env vars alone
Section titled “When to use firma run vs proxy env vars alone”Use proxy env vars alone when:
- You’re developing OpenFirma itself or a policy bundle.
- The agent is your own code, running on a machine you trust, and you’re using OpenFirma for audit and policy, not for containment.
- You want the lowest-friction setup — no sandbox, just
HTTPS_PROXY=….
Use firma run when:
- The agent is third-party, untrusted, or runs prompts you don’t fully control.
- You want a hard guarantee that nothing escapes the policy boundary, and you can use a structural backend.
- You’re shipping an agent runtime to others and the policy boundary is part of the product (rather than an add-on operators have to remember to wire up).
- Your threat model includes a compromised agent process that might actively try to evade enforcement.
Important: If you are on macOS or WSL2 and your threat model includes a non-cooperative or adversarial agent, proxy-only enforcement may not be sufficient. Consider running firma run on a Linux host with the bwrap backend for structural confinement, or accept the proxy-only limitation explicitly via --allow-non-structural.
For the macOS parity decision, including the capability matrix and ESF caveats, see macOS structural confinement strategy.
For a worked example of using firma run to govern a real coding agent, see Secure a local coding agent.
Where to go next
Section titled “Where to go next”- Wrap an agent with
firma run— the operator-side walkthrough. - Threat model & bypasses — what’s in scope and what’s not.
- Interception — what happens once traffic reaches the Sidecar.