← All docs

OS-native sandbox

How agentjail-shield wraps your agent in the kernel sandbox so shell tricks and subprocess spawns can't bypass policy.

agentjail-shield wraps your coding agent in the operating system’s kernel sandbox before exec’ing it. Every subprocess the agent spawns inherits the restrictions, so tricks like printf x > ~/.ssh/id_rsa, eval $(base64 -d), or python -c "open(...).write(...)" all return EPERM at the kernel level, regardless of any hook bypass.

This is the OS-native enforcement layer in agentjail’s isolation model, stronger than hooks alone.

Quick start

Run your agent through the sandbox with agentjail claude (or agentjail run -- codex / agentjail run -- cursor) - it shields by default:

agentjail claude

The shield is a role of the single agentjail binary. You can invoke it directly on any command through the agentjail-shield symlink:

agentjail-shield -- sh -c "cat ~/.ssh/id_rsa"
# → Operation not permitted

To run hook-only, without the OS sandbox, add --no-sandbox (agentjail claude --no-sandbox).

How it works

macOS: Apple Seatbelt

agentjail-shield generates an Apple Seatbelt (sbpl) profile from your policy config and execs the agent under sandbox-exec. The profile is deny-list based (allow-by-default):

  • Denies writes to sensitive paths: ~/.ssh, ~/.aws, ~/.gnupg, ~/.config, ~/.agentjail, ~/.docker, ~/.kube, ~/.cargo, ~/Library/Keychains, ~/Downloads, ~/Desktop, /etc, /var
  • Denies writes matching sensitive filenames: .env*, *.pem, *.key, id_rsa, credentials, .netrc, ~/.npmrc, ~/.pypirc, ~/.git-credentials
  • Denies reads of credential paths: ~/.ssh, ~/.aws, ~/.gnupg, ~/.docker, ~/.kube, ~/Library/Keychains, private key files
  • Allows reads of system trust stores (/private/etc/ssl, /System/Library/Keychains) so TLS works
  • Restricts network egress (see Network enforcement)

No sudo, no entitlement, no Developer ID required. sandbox-exec ships on every macOS since 10.5.

Linux: Landlock LSM

On Linux, agentjail-shield uses Landlock (available since Linux 5.13). Landlock is allowlist-based, the opposite of the macOS deny-list:

Allowed (read-write)Allowed (read-only)Denied
/tmp, current working directory/usr, /bin, /lib, /lib64, /sbin, /etc, /dev, /proc, /sys, /opt, /run, $HOME (excluding sensitive subdirs)Everything else

Sensitive subdirectories (~/.ssh, ~/.aws, ~/.gnupg, ~/.agentjail, ~/.config) are never added to the allowlist, so they are denied by default.

Landlock restrictions are irreversible: once applied, neither the process nor its descendants can lift them. No special privileges required.

Other platforms

On unsupported platforms (Windows, FreeBSD, etc.), agentjail-shield prints a warning and execs the agent without any sandbox (fail-open). The hook layer (agentjail-hook) still runs on every tool call.

Network capture and enforcement

The shield captures the agent’s network traffic and checks it against network.allowed_hosts. This works on both Linux and macOS.

Capture gateway (default)

The LLM provider call (POST /v1/messages) is captured by default through a base-URL capture gateway - on macOS this needs no system extension, because the shield points the agent’s own ANTHROPIC_BASE_URL at a per-session loopback proxy that records the request/response and forwards to the real provider over TLS.

Transparent tunnel (opt in with --tunnel)

To capture and enforce all of the agent’s traffic - not just the provider call

  • opt into the transparent tunnel with --tunnel. It routes everything through agentjail’s gVisor + MITM engine (network namespaces on Linux, a system extension on macOS) and enforces network.allowed_hosts, with HTTP/2 and gRPC support.

Run agentjail doctor to see the effective value of every network knob and where it came from (CLI flag > env var > policy.yaml > default, per ADR 0110).

--netproxy (deprecated)

The older egress firewall - agentjail-netproxy, a localhost CONNECT proxy the agent’s HTTPS_PROXY points at - still works but is deprecated in favor of the tunnel.

Relationship to the hook layer

The sandbox does not replace the hook (agentjail-hook + agentjail-daemon). They serve complementary roles:

CapabilityHookSandbox
MCP server allowlistingYesNo
Command-intent rules (git push --force)YesNo
Tell the agent why something was blockedYesNo
UX decisions (allow / deny / ask)YesNo (deny only)
Catch shell/eval/Python file writesNoYes (kernel-level)
Catch subprocess bypassNoYes (inherited by descendants)
Network per-host enforcementNoYes (via the tunnel, both platforms)

Use both for defense in depth. The hook catches the 90% case with good UX; the sandbox is the safety net that catches the rest.

Configuration

The shield reads the same ~/.agentjail/policy.yaml as the hook and daemon. The relevant sections:

file.extra_deny

Additional write-denied paths, appended to the built-in sensitive path list:

file:
  extra_deny:
    - /Users/me/secret-project
    - /opt/production-data

file.extra_allow

Additional write-allowed paths (Linux Landlock only; no effect on macOS):

file:
  extra_allow:
    - /data/scratch

network.allowed_hosts

Hosts the agent can reach, enforced by the transparent tunnel (both platforms):

network:
  allowed_hosts:
    - api.github.com
    - registry.npmjs.org
    - "*.example.com"

Wildcards follow cert-style matching: *.example.com matches foo.example.com but not example.com itself. Built-in defaults include GitHub, npm, PyPI, crates.io, Go proxy, and Deno.

CLI reference

agentjail-shield [flags] -- <agent-cmd> [args...]

The -- separator is required.

FlagDefaultDescription
--policy=PATH~/.agentjail/policy.yamlPath to the policy config file
--profile-printfalsePrint the generated sandbox profile to stderr and exit
--tunnelfalseCapture and enforce all traffic through the transparent tunnel
--no-provider-gatewayfalseDisable the base-URL LLM capture gateway
--no-sandboxfalseRun hook-only, without the OS sandbox

See agentjail doctor for the effective network configuration and precedence (ADR 0110); --netproxy is deprecated in favor of --tunnel.

Debugging

# Print the generated profile without running the agent
agentjail-shield --profile-print -- claude

# Test a specific operation
agentjail-shield -- sh -c "echo test > ~/.ssh/test_file"
# Expected: "Operation not permitted" on macOS

Fail behavior

ScenarioBehavior
sandbox-exec missing (macOS)Fail-open with loud warning; hook still active
Landlock unsupported (Linux < 5.13)Fail-open with loud warning
Landlock setup error (other)Fail-closed unless AGENTJAIL_SHIELD_ALLOW_UNSANDBOXED=1
policy.yaml missingFalls back to built-in defaults
agentjail-netproxy not foundFalls back to port-based filtering
Unsupported platformFail-open with warning

Next steps