# Wrap agent terminal commands in macOS Seatbelt sandbox \(\#57431\) · gitcafe/zed

[View on GitCafe](https://git.cafe/gitcafe/zed/commit/f0341c96a17630e5dfb441701441945dbb2a605a)

Repository: [gitcafe/zed](https://git.cafe/gitcafe/zed)

Visibility: public

Requested revision: f0341c96a17630e5dfb441701441945dbb2a605a

Requested commit: f0341c96a17630e5dfb441701441945dbb2a605a

Commit: f0341c96a17630e5dfb441701441945dbb2a605a

Tree: 832fc3c88af27f87fefcefc95ccbd63a2d58b2cd

Author: Richard Feldman

Committer: GitHub

## Message

```
Wrap agent terminal commands in macOS Seatbelt sandbox (#57431)

Stacked on top of #57430.

When the `sandboxing` feature flag is on (macOS only), agent-run
terminal commands are launched under `/usr/bin/sandbox-exec` with a
per-command Seatbelt policy:

- **Reads:** any path on the filesystem.
- **Writes:** each project worktree plus a per-command `$TMPDIR`.
- **Network:** blocked.

The model can request relaxations on individual calls via three new
input flags on the `terminal` tool:

- `allow_network: true`
- `allow_fs_write: true`
- `unsandboxed: true`

Each one triggers a separate always-prompt user approval (bypassing any
`always_allow` rules, since escalation is a stronger trust boundary than
the baseline command approval). The flags are silently ignored when
sandboxing is off, so the model can't surreptitiously change runtime
behavior by setting them in the no-op case.

The per-command tempdir is provisioned regardless of sandbox state so
the model can't infer whether the sandbox is in effect by looking at
`$TMPDIR`.

The Seatbelt config file's lifetime is tied to the `Terminal` entity via
an opaque RAII handle (`SandboxConfigHandle = Box<dyn Any + Send>`), so
cancellation paths drop it automatically.

### Notes for review

- The writable scope passed to the sandbox is the project's worktree
paths plus the per-command tempdir, **not** the resolved `cd` working
directory — `cd` is model-controlled, and using it as the writable scope
would let the model widen its own write permissions outside the project.
- The new tool flags are always present in the JSON schema (with
`#[serde(default)]`), even when the sandbox prompt section isn't
rendered. The system prompt only documents them when the section is
present, so the model shouldn't try to use them when it's not — but the
schema doesn't actively reject them, just ignores them.
- I opted not to add an end-to-end test for the escalation prompt path:
the existing `FakeThreadEnvironment` ignores the new params and toggling
the feature flag in tests is more wiring than felt worth it for a first
cut. The pure-function pieces (`sandbox_approval_title`, schema,
deserialization) are covered, and the sandbox crate itself has
end-to-end tests that actually invoke `sandbox-exec`.

Release Notes:

- N/A

---------

Co-authored-by: MartinYe1234 <52641447+MartinYe1234@users.noreply.github.com>
```

## Parents

- [ffbda20db176f65402ab9f61b723fa1e435df81e](https://git.cafe/gitcafe/zed/commit/ffbda20db176f65402ab9f61b723fa1e435df81e?format=markdown)

[Source at this commit](https://git.cafe/gitcafe/zed/tree/f0341c96a17630e5dfb441701441945dbb2a605a?format=markdown)
