← 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. When an explicit grant covers a regular file directly under ~/.config, the shield applies file-scoped Landlock rights. It does not grant the directory or expose sensitive config subdirectories.

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.

This remains opt-in for a direct agentjail run -- <agent> launch. If you chose agentjail install --with-path-shim, the installed claude, codex, and Cursor agent wrappers use the transparent tunnel by default.

Use --require-tunnel or AGENTJAIL_REQUIRE_TUNNEL=1 when a fallback would invalidate a test or release claim. AgentJail then refuses to launch if the transparent tunnel cannot start.

On macOS, the full transparent tunnel requires the separately built and approved Network Extension application. The AgentJail CLI tarball does not package, install, activate, or approve that extension. The default base-URL provider capture path above does not need it.

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
--require-tunnelfalseRefuse fallback if the transparent tunnel cannot start
--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.

Practical guides

  • Claude Code sandbox: set up and verify the hook, kernel boundary, network controls, and MCP policy together.
  • AI agent security: place sandboxing inside a broader model for credentials, tool calls, and audit logs.

Debugging

# Mirror structured startup diagnostics to stderr
agentjail run --verbose -- codex

# 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

Routine launches print one to three lines. Full structured diagnostics are kept under ~/.agentjail/logs/; AgentJail retains the newest 10 session files. The directory is mode 0700, and each file is mode 0600.

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