sandboxing the Cursor agent with bubblewrap

Introduction

The Cursor CLI agent (cursor-agent) is useful for coding in a terminal, but by default it runs with the same view of your machine as you do. For everyday work I want it focused on the current project — not casually reading .ssh, shell history, or unrelated files in my home directory.

So I wrote a small bash wrapper, cursor-agent-sandboxed, that starts the agent inside bubblewrap (bwrap). The agent gets the project directory, an isolated home under /home/agent, a selective /usr/bin allowlist, and just enough of the system to run tools and talk to the Cursor API. Longer notes live in cursor-agent-sandboxed.md.

Requirements: Linux with user namespaces, bwrap on the PATH (package bubblewrap), cursor-agent under /opt/cursor-agent, and host binaries /usr/bin/node and /usr/bin/rg (ripgrep).

Quick start

Put the wrapper somewhere on your PATH, then:

cd /path/to/project
cursor-agent-sandboxed

That is roughly cursor-agent --continue inside the sandbox, so the last chat session continues when possible.

cursor-agent-sandboxed --version
cursor-agent-sandboxed --list-models
cursor-agent-sandboxed -f "short question"
PROJECT_DIR=/path/to/project cursor-agent-sandboxed
CURSOR_AGENT_SANDBOX_BINS="python3 make" cursor-agent-sandboxed

Session continue

By default the wrapper prepends --continue so the last session is loaded. Earlier versions used --resume, but that flag optionally takes a chat id and would swallow the next argument (for example a prompt) — hence --continue.

Auto-continue is skipped when you already pass:

Sessions need the real host store, so these directories are bind-mounted read/write into the sandbox home:

Progress is therefore written back to the host. That is deliberate, and it widens the writable surface compared to a fully disposable sandbox.

What is bubblewrap?

Bubblewrap builds an environment from Linux namespaces and bind mounts. You only see paths that were explicitly mounted (--ro-bind, --bind, --tmpfs). Anything else simply is not there inside the sandbox — stronger than many “deny list” approaches for filesystem metadata.

This setup uses separate PID, IPC, and UTS namespaces. The network stack stays on the host so HTTPS to the Cursor API keeps working. There is no container image and typically no root. The whole policy lives in the wrapper script.

How the wrapper works

  1. Resolve the login home with getent — never reuse an already isolated $HOME.
  2. Create a host-side sandbox home: ~/.local/state/cursor-agent-sandbox/home.
  3. Seed a few files from the real home (copies): ~/.config/cursor/auth.json, ~/.cursor/cli-config.json, ~/.cursor/agent-cli-state.json, and ~/.gitconfig if present.
  4. Build allowlist binds for /usr/bin/<name> (ALLOWED_BINS + CURSOR_AGENT_SANDBOX_BINS).
  5. Add --continue when appropriate.
  6. Exec bwrap, which runs /opt/cursor-agent/cursor-agent directly — not via PATH. The host symlink /usr/bin/cursor-agent is not mounted.

Inside the sandbox, HOME=/home/agent. Paths like /home/<user>/.bashrc do not exist there.

Useful environment variables:

/usr/bin allowlist

Unlike an earlier version of the wrapper, /usr/bin is not mounted as a whole. The script creates an empty /usr/bin and bind-mounts only named files read-only. Names missing on the host are skipped.

Built-in groups include:

Why node and rg must be listed: /opt/cursor-agent/node is a symlink to /usr/bin/node. Without that bind the agent dies with /opt/cursor-agent/node: No such file or directory. The agent also shells out to ripgrep; without rg you get Could not find ripgrep (rg) binary.

Extra tools for one run:

CURSOR_AGENT_SANDBOX_BINS="python3 make" cursor-agent-sandboxed

This is not a kernel exec whitelist. Everything under /usr/lib (including git helpers), /opt/cursor-agent, and the project directory stays reachable if the agent knows a path there.

Sandbox policy

Namespaces and runtime options include:

Important mounts:

Not mounted (examples): the rest of /usr/bin, /usr/src, /usr/include, the rest of the host home, /var, other /opt trees.

Security limits

What this typically blocks or makes awkward:

What it does not give you:

Checks and troubleshooting

Smoke tests:

cursor-agent-sandboxed --version
cursor-agent-sandboxed --list-models

Expect HOME=/home/agent, /usr/bin/node and /usr/bin/rg present, tools like python3 absent unless added via CURSOR_AGENT_SANDBOX_BINS, and no real .bashrc / /usr/src.

Common issues:

To tighten or loosen the policy, edit ALLOWED_BINS and the bwrap argument list in cursor-agent-sandboxed. For a narrower network, use the host firewall — or --unshare-net if you accept that the Cursor API will no longer work without a proxy.

References: bubblewrap on GitHub, Arch package bubblewrap.