Skip to content

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.

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.

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:

Terminal window
# macOS: must opt into proxy-only compatibility mode
firma run --profile generic --allow-non-structural -- curl https://example.com
# Or persist the opt-in in firma.toml:
# [run]
# allow_non_structural = true

Without the opt-in, firma run prints a typed error explaining that the selected backend provides proxy-only enforcement, and how to proceed.

firma run wraps the agent’s launch in a sandbox. The specific mechanisms depend on the backend:

  1. Network boundary. On Linux bwrap, the agent runs in a network namespace where the only reachable network destination is a host-side process listening on 127.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 run writes a launch contract that requires the configured guest runner to expose only the sidecar bridge and DNS stub to the guest.
  2. Proxy bridge. A small helper inside the sandbox listens on 127.0.0.1:18080 and forwards bytes over a Unix socket to the host’s Sidecar (typically at $XDG_RUNTIME_DIR/firma/sidecar.sock). On Linux bwrap, 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.
  3. DNS stub. A stub resolver answers DNS queries deterministically. On Linux it runs inside the sandbox path. On macOS structural paths, firma run starts 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.
  4. HTTP_PROXY injection. For agents that do respect proxy env vars, firma run sets HTTP_PROXY=http://127.0.0.1:18080 so they don’t need any code change.
  5. 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.

  1. HTTP_PROXY injection. firma run sets HTTP_PROXY, HTTPS_PROXY, and related environment variables pointing to the Sidecar. Agent processes that read these vars will route traffic through the Sidecar automatically.
  2. NO_PROXY clearing. Built-in profiles clear NO_PROXY / no_proxy to prevent a host environment variable from silently routing traffic around the Sidecar.
  3. Sandbox-exec (macOS only). On macOS, sandbox-exec provides filesystem-level confinement for the claude-code profile (blocking reads/writes to ~/.ssh, ~/.aws, etc.), but it does not provide mandatory network confinement.
  4. Require explicit opt-in. firma run fails closed unless --allow-non-structural is 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.

Network sandboxing primitives differ across OSes, so firma run selects a backend by platform:

BackendPlatformMechanismEnforcement
bwrapLinuxUnprivileged user namespaces (bubblewrap)Structural
firecrackerLinuxKVM micro-VMPlanned
vzmacOSHost proxy bridge + HTTP proxy injection (HTTP_PROXY); optional sandbox-exec network-deny or VZ guest runner contractProxy-only (default); experimental structural modes via FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1 or FIRMA_RUN_VZ_GUEST=1
wsl2WindowsHTTP proxy injection (HTTP_PROXY)Proxy-only

You can override the platform default with --backend:

Terminal window
firma run --profile generic --backend firecracker -- python my_agent.py

The 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.

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.

OSModes and release stance
Linuxbwrap: current structural release path.
firecracker: planned backend; no current release claim.
macOSvz 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 / WSL2wsl2: proxy-only compatibility path.

Available modes:

  • bwrap - current Linux default.
  • firecracker - planned Linux micro-VM backend.
Runtime invariantbwrapfirecracker
Sidecar-only egressYes. Network namespace makes the proxy bridge the only useful exit.Planned. KVM micro-VM network isolation target.
DNS confinementYes. Sandbox resolver points at the local DNS stub or fails closed.Planned. Guest-local deterministic resolver target.
Fail-closed startupYes. Backend, sidecar, policy, and seccomp setup failures block launch.Planned.
Fail-closed runtimeYes. With no direct egress route, sidecar or bridge loss breaks outbound traffic.Planned.
Child/process-tree bypass resistanceYes. Child processes inherit the network namespace.Planned.
Syscall/seccomp enforcementLinux-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 envelopeYes. Runtime fixes identity, env, mounts, routing, and optional seccomp before launch.Planned.
Interactive CLI/TUI supportYes. Stdio, signals, and exit status are preserved through the wrapper path.Planned.
Evidence statusRuntime code, Linux E2E harness, and FIR-111 seccomp spike artifacts exist.Planned backend; no release evidence.

Available modes:

  • vz default - proxy-only host compatibility mode.
  • vz + FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1 - experimental sandbox-exec network-deny mode.
  • vz + FIRMA_RUN_VZ_GUEST=1 - experimental VZ guest runner contract mode.
Runtime invariantStatus
Sidecar-only egressNo. Cooperative HTTP clients are mediated through injected proxy env.
DNS confinementNo. Host DNS remains available to non-cooperative processes.
Fail-closed startupYes for startup. Launch is blocked unless proxy-only mode is explicitly accepted and the sidecar path is prepared.
Fail-closed runtimePartial. Proxy-routed clients fail, but direct sockets can bypass.
Child/process-tree bypass resistanceNo. Children can ignore or clear proxy env.
Syscall/seccomp enforcementNo. seccomp is unavailable on macOS.
Immutable execution envelopePartial. Runtime creates a deterministic launch envelope, but proxy-only children can still bypass network intent.
Interactive CLI/TUI supportYes. Host-process compatibility path preserves normal CLI behavior.
Evidence statusRuntime proof logs and unit tests support the proxy-only claim.
Runtime invariantStatus
Sidecar-only egressPartial / experimental. External IP egress is denied by sandbox-exec, but all loopback remains reachable.
DNS confinementPartial / experimental. Non-loopback DNS is blocked by network denial; the host resolver is not replaced.
Fail-closed startupYes / experimental. Network-deny mode still uses the same startup fail-closed path.
Fail-closed runtimePartial / experimental. External egress remains denied, but loopback-all scope is a residual caveat.
Child/process-tree bypass resistancePartial / experimental. Child processes should inherit the MAC sandbox label for external egress denial; loopback remains reachable and hardware E2E is pending.
Syscall/seccomp enforcementNo seccomp. Uses TrustedBSD MAC network rules, not syscall filtering.
Immutable execution envelopePartial / experimental. Launch envelope plus MAC profile are fixed before process start; residual loopback caveat remains.
Interactive CLI/TUI supportYes / experimental. Still host-process based through sandbox-exec.
Evidence statusRuntime code and unit tests exist; macOS hardware E2E assertions are written but not yet green evidence.
Runtime invariantStatus
Sidecar-only egressTarget / experimental. Launch contract requires bridge-only guest egress; the runner and guest image must enforce it.
DNS confinementTarget / experimental. Contract carries the DNS stub address; the runner must wire guest DNS to it.
Fail-closed startupTarget / experimental. Artifact validation and contract generation fail closed; runner-side preflight must also prove the guest boundary.
Fail-closed runtimeTarget / experimental. Guest route and bridge loss behavior must be proven by the runner and hardware E2E tests.
Child/process-tree bypass resistanceTarget / experimental. Guest boundary should cover the process tree; runner and guest proof are pending.
Syscall/seccomp enforcementTarget only. Contract can carry a seccomp filter path for an in-guest Linux runner; guest loading is not a current release claim.
Immutable execution envelopeTarget / experimental. Versioned launch contract records command, env, mounts, network endpoints, and required invariants.
Interactive CLI/TUI supportTarget / experimental. Runner must preserve stdio, signals, exit status, terminal resize, and TTY behavior.
Evidence statusRuntime contract code and unit tests exist; signed VZ runner, guest image lifecycle, route proof, and hardware E2E are still pending.

Available mode:

  • wsl2 - proxy-only compatibility mode.
Runtime invariantwsl2
Sidecar-only egressNo. Cooperative proxy env only.
DNS confinementNo. No mandatory DNS boundary.
Fail-closed startupYes for startup. Launch is blocked unless proxy-only mode is explicitly accepted and the sidecar path is prepared.
Fail-closed runtimePartial. Proxy-routed clients fail, but direct sockets can bypass.
Child/process-tree bypass resistanceNo. Children can ignore or clear proxy env.
Syscall/seccomp enforcementNo current claim.
Immutable execution envelopePartial. Runtime injects env and identity into the WSL launch, but no structural boundary backs it.
Interactive CLI/TUI supportPartial. Basic process execution is supported; WSL terminal behavior depends on host setup.
Evidence statusRuntime code and unit tests support the proxy-only claim.

These limits are part of the current release posture. Treat them as constraints on what firma run can claim today, not as implementation bugs.

LimitCurrent statusPractical effect
macOS default vz is proxy-onlyThe 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-scopedFIRMA_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 claimFIRMA_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 completeThe 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-onlyThe 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 todayManaged 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.

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_PROXY set. 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 and sandbox-exec confinement 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:

Terminal window
firma run --profile generic --print-effective-config -- echo hi

This prints the resolved config as JSON so you can see exactly what mounts, env vars, and identity remaps will be applied.

The preferred runtime shape keeps capability material outside the agent process:

  1. Before the sandbox starts, the operator stages capability material for the Sidecar, normally through [capability_seed].
  2. The host-side Sidecar reads that seed outside the sandbox.
  3. Inside the sandbox, the agent only needs HTTP_PROXY=http://127.0.0.1:18080.
  4. 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 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.

  • 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_PROXY might 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.
  • 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.