# Sandboxing

> Source: <https://zed.dev/blog/sandboxing>
> Published: 2026-08-05 17:26:29+00:00

Zed now supports [sandboxing](https://zed.dev/docs/ai/sandboxing) within the agent panel.

Sandboxing restricts what the agent is able to do when using the `terminal`

and `fetch`

tools. These restrictions are enforced by the operating system and do
not rely on an agent following instructions.

Sandboxing is enabled by default for all users, starting on the 1.14 release.

[Why sandboxing?](#why-sandboxing)

Some users prefer to tightly control what the agent is allowed to do, while others opt for "YOLO mode" and give agents freedom to do anything and everything on their computer.

There are advantages and downsides to both approaches. Manually approving/denying each action the agent takes can get tedious, and sacrifices some of the automation which agents provide. On the other hand, an agent in YOLO mode can wreak havoc on your machine (or any machines you can reach over the network).

The tension comes from the fact that agents cannot be trusted to determine
whether an action is something the user would want. You may be disappointed to
learn that we are not announcing that we have solved this [famously hard
problem](https://en.wikipedia.org/wiki/AI_alignment). Until we do, sandboxing is the best way to constrain an agent's behavior.

[Sandboxing in Zed](#sandboxing-in-zed)

In Zed, the [agent panel](https://zed.dev/docs/ai/agent-panel)'s `terminal`

and `fetch`

tools 1 are now sandboxed by default. The default sandbox rules forbid an agent from writing outside the
project directories, writing to

`.git`

, or making network requests.For many interactions, this is more than enough. But when it's not, agents can request permission to temporarily escalate their privileges. The user will see:

- which privileges the agent is asking for
- a reason why they're asking for it

Note: The agent maynotrequest write access to`.git`

, since this allows an agent to write hooks that run outside the sandbox.

[Implementation](#implementation)

Sandboxes are implemented using operating system APIs:

- macOS uses Seatbelt
- Linux uses namespaces (through Bubblewrap)
- Windows uses WSL
- Non-WSL shells on Windows do not support sandboxing

Linux users will need to make sure they have a working `bwrap`

binary without
the `setuid`

bit set in their `$PATH`

. WSL users will need to make sure this is true inside their WSL environment.

[Can't I just tell my agent not to edit certain files?](#cant-i-just-tell-my-agent-not-to-edit-certain-files)

You can, and a lot of the time, that's sufficient. Modern LLMs are pretty good
at following instructions, but it's not a guarantee. Instructions also do very little to protect against [prompt injection
attacks](https://en.wikipedia.org/wiki/Prompt_injection).

Perhaps you work on an open source project, and you're reviewing a contributor's PR. You open an agent and ask "review this PR". Little do you know, however,
that the PR contains a modified `AGENTS.md`

that instructs your agent to upload
`$MY_SECRET_API_KEY`

to a server controlled by the attacker.

This has [already happened](https://labs.cloudsecurityalliance.org/research/csa-research-note-comment-control-github-prompt-injection-20/), and it's only going to get more common.

The benefit of sandboxes is that they simply do not allow access to certain resources 2.

[But what about fine-grained rules?](#but-what-about-fine-grained-rules)

Zed has supported fine-grained rules for the `terminal`

tool for a while. You
can, for example, disallow any command that matches `git .*`

. Why bother with
sandboxing when we could ship a set of rules that achieves the same
restrictions?

The answer is that it's simply not possible. A rule that bans `git .*`

does
very little to prevent an agent that *really wants* to modify your `.git`

folder. It can:

`bash -c 'git ...'`

`EVIL_CMD="git ..." bash -c $EVIL_CMD`

`echo 'git @$' > evil_git; chmod +x evil_git; evil_git ...`

`python ...`

- the list goes on...

Fine-grained rules work well as a guideline when dealing with a well-aligned agent. They fall over instantly in the presence of an even vaguely sophisticated attacker.

[Sandboxes are tricky to get right](#sandboxes-are-tricky-to-get-right)

Sandboxes are similar to other software features in many respects, but differ in a couple of key ways:

- A single bug may compromise a user's security.
- There must be no bugs
*even in the presence of adversaries*.

This means that you can't just say "Oh, this won't happen in practice". Instead, you need to ask:

Could an attacker make this happen?

[Symlink swaps](#symlink-swaps)

A good example of this is what I've been calling the "symlink swap" attack. Note
that Zed's sandbox *does* catch this attack and will fail-closed, meaning that
the untrusted command will not be run.

Some background: on Linux, Zed's sandbox controls filesystem access
using Bubblewrap. This is a program called `bwrap`

which allows running a
command within a "namespace" 3. The simplest

`bwrap`

invocation might be:This command:

- creates a new namespace
- mounts the real
`/`

into the namespace at`/`

, read-only - runs
`echo hello`

within the namespace

Note that, without `--ro-bind / /`

, this command would fail, since it wouldn't
be able to find `echo`

.

When Zed creates a sandbox with access to specific directories, it constructs a
set of `--bind`

or `--ro-bind`

arguments to pass to `bwrap`

.

The flow for the symlink swap attack goes like this:

- A malicious agent is given write access to
`/foo`

. - It then spawns two subagents which run in parallel:
- Subagent 1 repeatedly tries to write the attacker's public key to
`/foo/bar/.ssh/authorized_keys`

- Critically, it also requests write access to
`/foo/bar`

. A user is likely to grant this, since it already has`/foo`

, so it seems safe.

- Critically, it also requests write access to
- Subagent 2 replaces
`/foo/bar`

with a symlink to`/home/alice`

- Note that this can succeed even if the sandbox only grants
**read** access to`/home/alice`

- Note that this can succeed even if the sandbox only grants

- Subagent 1 repeatedly tries to write the attacker's public key to
- Initially, the writes to
`/foo/bar/.ssh/authorized_keys`

fail, since there's no such file. - However, after the second subagent's symlink swap succeeds, the path
`/foo/bar/.ssh/authorized_keys`

points to`/home/alice/.ssh/authorized_keys`

. - But this is still blocked by the sandbox, since write access to
`/home/alice`

was never granted.

So far, so good. But there's a catch! There's a small timing window between:

- the time when the user is shown the prompt and clicks "allow"
- the time when the path is given to
`bwrap`

If the symlink swap happens in that window, then:

- the user sees
`/foo/bar`

and approves `/foo/bar`

becomes a symlink to`/home/alice`

- Zed passes
`--bind /foo/bar /foo/bar`

to`bwrap`

- Within the sandbox,
`/foo/bar`

is mounted to the real`/home/alice`

, with write access.

At this point, the sandboxed program can write to
`/foo/bar/.ssh/authorized_keys`

, which resolves to
`/home/alice/.ssh/authorized_keys`

. The attacker's public key is now trusted by
the machine!

This is a classic *time-of-check-time-of-use* (TOCTOU) bug. Left unaddressed, it
would allow a malicious agent to gain write access to any directory it has read
access to.

**In practice**, the timing window is incredibly small - it's essentially just
the time it takes to spawn a subprocess - usually a few hundred microseconds.
But that's not good enough. Attackers can repeatedly try 4, and they only need to
succeed once.

[How safe is the sandbox?](#how-safe-is-the-sandbox)

Sandboxes are just one layer in a [defense in depth](https://en.wikipedia.org/wiki/Defense_in_depth_(computing)) strategy.
For more detail, check out our [docs on this topic](https://zed.dev/docs/ai/privacy-and-security).

The sandbox covers the `terminal`

and `fetch`

tools in Zed's agent, but it does
*nothing* to protect you when you use:

- Other tools in the Zed agent like the
`edit`

tool. - Agents connected over
[ACP](https://zed.dev/acp) - LSP and MCP servers
- The regular built-in terminal
- External programs like
`ghostty`

or VSCode

A malicious agent may not be able to execute `pwn_my_machine.sh`

from within the
Zed agent's terminal, but it *might not need to*, if it can trick you into
running it outside the sandbox.

For example:

- It could add a
`build.rs`

file that runs the script. Then, when you run[5](#user-content-fn-5)`cargo run`

in the built-in terminal, it executes that`build.rs`

*outside the sandbox*. - If given access to write to
`/home/alice`

, it could edit your`.bashrc`

to run the script*outside the sandbox*. - It could edit your project using regular editing tools to contain a Rust crate
with a malicious procedural macro
that runs the script.[6](#user-content-fn-6)`rust-analyzer`

will then build and run this macro*outside the sandbox*. - If your project contains a Git submodule, it could install a hook that runs
the script when you commit. If you run
`git commit`

from a regular terminal app, then the hook would run*outside the sandbox*.

The list is almost endless. Closing everything requires more than what sandboxing alone can provide.

[Wrapping up](#wrapping-up)

Sandboxing is now enabled by default in Zed's agent panel. The `terminal`

and
`fetch`

tools run with restrictions enforced by the operating system: agents
can't write outside your project directories, can't touch `.git`

, and can't
reach the network unless you grant access. When an agent needs more, it has to
ask, with a reason you can evaluate. As one layer in a security strategy, it
meaningfully limits what an agent can do to your machine.

[Footnotes](#footnote-label)

-
The

`create_directory`

tool, while not strictly "sandboxed", participates in the sandboxing permissions flow. Note that, even when permission has*not*been granted, the`create_directory`

tool may temporarily create the requested directory, but will clean it up if permission is not granted.[↩](#user-content-fnref-1) -
Assuming no bugs in the sandbox implementation.

[↩](#user-content-fnref-2) -
Namespaces are the core primitive provided by the Linux kernel that underpin sandboxes. They allow creating a context in which a user can run a program where it gets a simulated OS environment (i.e. it has a different view of the filesystem, devices, users, etc.).

[↩](#user-content-fnref-3) -
There are also techniques that can widen this gap, which makes it even easier to exploit.

[↩](#user-content-fnref-4) -
A

`build.rs`

is a special file in a Rust project that runs code before compiling your programor library.[↩](#user-content-fnref-5) -
A procedural macro is another Rust feature that allows writing a program that manipulates Rust source code (for example,

`#[derive(Serialize)]`

is a procedural macro). While they are typically pure functions, they are not required to be, and can even do some pretty cursed things like[forking the compiler](https://crates.io/crates/whichever-compiles/1.0.0)![↩](#user-content-fnref-6)

### Related Posts

Check out similar blogs from the Zed team.

### Looking for a better editor?

You can try Zed today on macOS, Windows, or Linux. [Download now](/download)!

### We are hiring!

If you're passionate about the topics we cover on our blog, please consider [joining our team](/jobs) to help us ship the future of software development.
