> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# OS Sandbox

> Kernel-enforced containment for agent shell commands — Seatbelt on macOS, bwrap on Linux

The OS sandbox puts a real `/bin/sh -c` inside a kernel-enforced filesystem jail — `sandbox-exec` (Seatbelt) on macOS, `bwrap` (bubblewrap) on Linux — and refuses to run unless it has measured that the jail actually blocks writes. Under `PRAISON_SHELL=sandboxed` this applies to **every** `execute_command`, not only commands with shell operators.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "OS Sandbox"
        Cmd["🖥️ Shell command"] --> Probe{"🔍 Enforcement<br/>proven?"}
        Probe -->|blocked out-of-jail write| Jail["🛡️ Run inside jail"]
        Probe -->|write escaped| Refuse["🚫 Refuse — never degrade"]
    end

    classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef check fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef safe fill:#10B981,stroke:#7C90A0,color:#fff
    classDef refuse fill:#6366F1,stroke:#7C90A0,color:#fff

    class Cmd input
    class Probe check
    class Jail safe
    class Refuse refuse
```

## Quick Start

**1. Run the coding agent sandboxed**

`PRAISON_SHELL=sandboxed` drives the OS sandbox — a real shell inside Seatbelt or bwrap.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
PRAISON_SHELL=sandboxed praisonai code
```

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
>>> run: echo written > r2.txt
exit=0 success=True sandbox=seatbelt; r2.txt contents 'written'
```

**2. Watch an escape get denied by the kernel**

A write outside the workspace hits a real OS denial, not a soft warning.

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
>>> run: echo pwned > /tmp/PWNED.txt
/bin/sh: /tmp/PWNED.txt: Operation not permitted
PWNED exists: False
```

***

## Two Backends

Each platform uses its native primitive; everywhere else, no wrapper is built.

| Platform | Backend | How it confines |
| - | - | - |
| macOS | `sandbox-exec` (Seatbelt) | Generated profile denies `file-write*` outside the writable set, and denies reads and writes under the masked secret dirs |
| Linux | `bwrap` (bubblewrap) | Read-only root, workspace bind-mounted writable, `--tmpfs` over the masked secret dirs, `--unshare-pid`, network unshared |
| Other | none | `build_wrapper()` returns `None` — containment unavailable |

***

## Workspace and cwd

The writable set is `PRAISONAI_WORKSPACE` (or the process cwd) plus `$TMPDIR`. The tool's `cwd=` argument must be inside that set, or the command is refused — the model cannot promote a path to writable by passing it as `cwd`. Network is denied by default.

***

## Environment and masked paths

**Env scrub.** Variables whose names match `KEY|TOKEN|SECRET|PASSW|CREDENTIAL` (case-insensitive) are not passed into the jail. Entries passed explicitly through `execute_command(env=...)` are the allow-list and still go through.

**Env applied inside the jail.** Explicit `env=` entries are set by `/usr/bin/env k=v …` inside the jail, never on the `bwrap` / `sandbox-exec` launcher — otherwise `LD_PRELOAD` and friends would load code before containment starts.

**Masked dirs.** `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config/gcloud` and `~/.azure` are hidden (`--tmpfs` on bwrap, `deny file-read* file-write*` on Seatbelt). The rest of the filesystem stays readable because toolchains need it.

**Own PID namespace (Linux).** `--unshare-pid` makes the fresh `/proc` show only the jail, so `/proc/<host pid>/environ` cannot reveal the scrubbed env.

> [!NOTE]
>
> **Model cannot override the jail.** The tool-facing `execute_command` drops `mode`, `writable_paths` and `network` from its arguments — the jail is an operator decision.

***

## Refuse, Never Degrade

A sandbox that is assumed but not enforcing is worse than none — callers relax on a belief that isn't true.

`PRAISON_SHELL=sandboxed` refuses to run when the OS primitive is missing or not enforcing, rather than silently falling back to an uncontained shell. To accept an uncontained shell deliberately, use `PRAISON_SHELL=unsafe`.

***

## Probe Before Claiming

`probe_enforcement()` measures the jail instead of trusting it.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Model
    participant execute_command
    participant Probe as probe_enforcement()
    participant Kernel

    Model->>execute_command: echo written > r2.txt
    execute_command->>Probe: is the jail enforcing?
    Probe->>Kernel: run child, write in-jail canary + out-of-jail escape
    Kernel-->>Probe: canary written, escape blocked
    Probe-->>execute_command: (True, 'seatbelt', 'blocked out-of-jail write')
    execute_command->>Model: approve? (critical risk)
    Model-->>execute_command: yes
    execute_command->>Kernel: /bin/sh -c inside jail (own PID namespace on Linux)
    Kernel-->>Model: exit=0, contained
```

The probe runs a child under the wrapper that writes an in-jail canary **and** attempts a write outside the writable set:

* If the escape write **succeeds**, the backend is reported unavailable.
* The canary guards against a false positive — if the wrapper couldn't start the child at all, the missing escape file proves nothing, so the canary must exist for the result to count.

Results are `(enforcing: bool, backend: str, detail: str)` and cached per process. Flipping to a permissive Seatbelt profile flips the probe from `(True, 'seatbelt', 'blocked …')` to `(False, 'seatbelt', 'did not block a write outside the writable set')`.

***

## Not `SandboxConfig.native()`

> [!WARNING]
>
> This OS sandbox is **not** `praisonaiagents.SandboxConfig.native()`. That call maps to the Linux-only `sandlock` backend and raises `ImportError` on macOS. The Seatbelt/`bwrap` primitives here live in the `praisonai code` CLI and are driven by [`PRAISON_SHELL=sandboxed`](https://praison.ai/docs/features/praison-shell) — this is the path that actually runs on macOS today.

***

## Best Practices

> [accordion] Trust the refusal
>
> If `sandboxed` mode refuses because containment can't be proven, install the primitive (`bwrap` on Linux) rather than switching to `unsafe`.

> [accordion] Scope the writable set
>
> Set `PRAISONAI_WORKSPACE` to the project root; only it and the temp dir are writable. Keep other paths out so an escape attempt is a real out-of-jail write the kernel can deny.

> [accordion] Leave network off unless needed
>
> `network=False` unshares the network on Linux and denies it in the Seatbelt profile. Enable it only when a build step genuinely needs it.

> [accordion] Pair with approval
>
> The sandbox runs behind the `critical`-risk `execute_command` approval — containment and human sign-off reinforce each other.

***

## Related

> [card] [Shell Modes](https://praison.ai/docs/features/praison-shell.md)
>
> The `PRAISON_SHELL` modes that drive this sandbox.

> [card] [Sandbox Guarantees](https://praison.ai/docs/features/sandbox-guarantees.md)
>
> What each sandbox surface actually isolates.

> [card] [Sandbox Backends](https://praison.ai/docs/features/sandbox.md)
>
> Docker, E2B, sandlock and other SDK backends.

> [card] [Tool Approval](https://praison.ai/docs/cli/tool-approval.md)
>
> The critical-risk wrap on the real-shell path.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.