Terminal, iTerm and the sandboxed shell
Terminal apps are the one case where ScopeShelf does not sandbox the app. It sandboxes the shell inside it. This page explains why, walks through every setting, and lists the error messages you can run into and what they mean.
- Why a terminal cannot run inside the sandbox
- What ScopeShelf does instead
- Setting up a terminal launcher in the app
- The Shell section, setting by setting
- Starting the sandboxed shell
- What you can and cannot do inside
- Doing it from the command line
- What the sandbox enforces, and what it does not
- Error messages and what to do
1. Why a terminal cannot run inside the sandbox
Terminal.app, iTerm2, Alacritty, kitty and friends do not start your shell directly. They go through /usr/bin/login, a small program that is marked setuid. macOS refuses to run setuid programs inside a Seatbelt sandbox, whatever the profile says. You can see it yourself:
$ sandbox-exec -p '(version 1)(allow default)' /usr/bin/login -fpl $USER /bin/echo hi sandbox-exec: execvp() of '/usr/bin/login' failed: Operation not permitted
So a terminal started inside the sandbox opens its window, tries to start a session, and fails. It looks like the app is broken. Nothing you configure in ScopeShelf can change that; it is a rule of the operating system.
ScopeShelf recognises these apps when you pick them. The card says Terminal — the sandbox applies to the shell you start here, the Start button becomes Start shell, and there is no Quit or "Restart protected" for them: quitting your terminal would take every open shell with it, for a start that cannot work.
2. What ScopeShelf does instead
Your terminal runs normally, unsandboxed. Inside it, ScopeShelf starts a sandboxed shell: a zsh (or whatever shell you use) wrapped in the launcher's Seatbelt profile. Everything you start from that shell inherits the sandbox: Claude Code, Codex, npm, build scripts, git. The shell sees your project folder and the folders you granted, and nothing else in your home directory.
The rules are the same three zones as for any launcher:
- Writable: the working folder of the shell (your project) and any folder you granted as writable.
- Read-only: reference folders, your shell startup files, program folders such as
~/.local/bin. - Invisible: everything else under your home directory.
~/.ssh,~/.gnupg,~/.aws,~/.config/gcloudand your keychains are blocked even if a granted folder contains them.
Everything outside your home directory (/usr, /opt/homebrew, /Applications, /tmp) stays reachable, as it is for every ScopeShelf launcher.
3. Setting up a terminal launcher in the app
- New launcher. Under step 1 click Terminal or iTerm in the row Or pick an installed app. You do not need the file dialog. If you prefer it: Terminal.app lives in
/System/Applications/Utilities/, and the dialog shows hidden folders, so you can navigate there or press Cmd+Shift+G and paste the path. - Name. The short name derived from it is what you type on the command line later (
scopeshelf shell my-name). - Grants. The Terminal and iTerm presets grant nothing by default. The project folder is asked for when you first press Start shell (see below), so you do not have to add it here. Add here what the tools inside need beyond the project. Typical for Claude Code with the native installer:
# Writable ~/.claude # settings, sessions ~/.local/share/claude # the binaries # Files (granted as single files) ~/.claude.json ~/.gitconfig
Reference material you only want the AI to read: add it as read-only. - Shell section (step 4). This is where the shell itself is configured. All lists are measured on your Mac, not guessed. Details in the next section.
- Save.
4. The Shell section, setting by setting
You find it under Edit launcher → step 4, Shell. The same settings exist once more under Settings → Shell as the default for every launcher; a launcher only overrides what you set in it.
| Setting | What it does |
|---|---|
| Program | Which shell runs. Default is your login shell ($SHELL). The list comes from /etc/shells. ScopeShelf knows zsh, bash, fish, nushell, ksh, sh, tcsh, elvish, xonsh and PowerShell (pwsh). Other shells start too, but ScopeShelf does not know their startup files. |
| Login shell | Starts the shell with -l, so it reads its profile files (.zprofile) like a fresh terminal window does. On by default. |
| Working folder | The shell's workspace: it starts here and may write here, even if the folder is not in the grants above. Leave it empty and the app asks for a folder the first time you press Start shell, then remembers it. From the command line, an empty working folder means "start where I ran the command" (see section 7). The home directory itself is refused as a working folder because a sandbox that can write your whole home protects nothing. |
| Startup files | The startup files of the chosen shell that actually exist on your Mac (~/.zshenv, ~/.zprofile, ~/.zshrc for zsh). Tick the ones the shell may read. Without them you get a bare shell: no prompt theme, no aliases, no PATH additions. |
| Files loaded by those startup files | ScopeShelf reads the ticked startup files and lists what they source themselves, for example ~/.cargo/env or ~/.nvm/nvm.sh. Tick them too, otherwise the shell prints operation not permitted for that line at every start. |
| Program folders | Folders from your $PATH that live inside your home directory: ~/.local/bin, ~/.bun/bin, ~/.cargo/bin, nvm's node folders and so on. Everything under home is invisible in the sandbox unless granted, so a tool installed there disappears without its tick. A bin folder brings its lib, libexec and share neighbours along, otherwise npm finds node but not its modules. |
| Own configuration and history in the sandbox | The shell gets its own configuration directory (ZDOTDIR for zsh, XDG_CONFIG_HOME for fish and nushell) under ~/Library/Application Support/ScopeShelf/shells/<short name>. ScopeShelf writes small bridge files there that load your real startup files, and the shell's history lands there too. Your own ~/.zsh_history is never touched by a sandboxed session. Shells without a movable configuration directory (bash, sh) run without their own history. |
5. Starting the sandboxed shell
From the app
Press Start shell on the launcher card, in the detail view, or in the menu bar menu. Two things happen:
- Working folder dialog (first time only, when no working folder is set): pick the project folder the shell should work in. ScopeShelf stores it in the launcher. It refuses your home directory and blocked folders such as
~/.ssh. - Your terminal comes to the front with a new window. ScopeShelf types the shell command into it. iTerm and Terminal.app are driven directly; any other terminal gets a window in Terminal.app.
The first time, macOS asks whether ScopeShelf may control your terminal (System Settings → Privacy & Security → Automation). Allow it, otherwise nothing opens.
A Dock starter (created in the detail view) does the same, including the folder dialog.
What the banner tells you
The command line tool currently prints its messages in German. This is what the lines mean:
Gesandboxte Shell (tttt, zsh) — beenden mit `exit`, Details mit `scopeshelf scope`. Schreibbar: ~/repos/my-project ~/.claude ~/.local/share/claude Nur lesbar: ~/.zshenv ~/.zshrc ~/.local/bin Arbeitsordner ~/repos/my-project ist fuer diese Shell schreibbar freigegeben (zusaetzlich zum Profil).
- Line 1: sandboxed shell, launcher name and shell; leave with
exit, details withscopeshelf scope. - Schreibbar (writable) and Nur lesbar (read-only) list your grants; the working folder comes first.
- Arbeitsordner … ist fuer diese Shell schreibbar freigegeben: the working folder was added as writable for this shell.
- Nicht freigegeben, aber von einer Startdatei geladen: … names files you should tick under Files loaded by those startup files.
- Netzwerk: blockiert or Netzwerk nur zu: … appears when the launcher restricts the network.
After the banner you are in your normal prompt, in the working folder. Start Claude Code, Codex or whatever you like. exit ends the sandboxed shell and returns you to the unsandboxed shell of that window.
6. What you can and cannot do inside
scopeshelf scopeprints the complete profile of the shell you are in: writable, read-only, blocked, network.cd ~works, butlsthere answers Operation not permitted: the home directory is invisible. That is the sandbox doing its job. Use the folders from the banner.- Writing outside the writable folders fails with Operation not permitted. Reading
~/.sshor the keychain fails the same way, always. - Everything you start inherits the rules: a build script cannot write where the shell cannot.
- With Block network or a domain filter on the launcher, outbound connections from this shell are blocked or limited to the listed domains. A blocked host is reported in the terminal as
scopeshelf: Netzwerk blockiert: host. - Your history is kept per launcher in the sandbox configuration directory, not in
~/.zsh_history.
7. Doing it from the command line
Install the command once under Settings → Command line tool. It links the scopeshelf binary that ships inside the app into ~/.local/bin (or /usr/local/bin if that folder is yours). Then, in any terminal window:
scopeshelf shell my-name # shell in the launcher's profile, in this window scopeshelf shell my-name --cd ~/repos/other # another project, just for this start scopeshelf shell my-name --shell /bin/bash # another shell, just for this start scopeshelf shell my-name --no-network # block outbound network for this start scopeshelf shell my-name -e FOO=bar # set an environment variable scopeshelf shell --app /Applications/iTerm.app ~/repos/x ~/docs:ro # ad hoc, without a saved launcher: x writable, docs read-only scopeshelf dry my-name # show the profile without starting anything scopeshelf scope # inside a sandboxed shell: what applies here?
Rules for the starting folder on the command line:
- A working folder set in the launcher, or given with
--cd, is the workspace and becomes writable for this shell. - Without either, the shell starts where you ran the command, but only if that folder is visible in the profile. Otherwise it tells you which folders are granted and stops. Where you happened to be is never granted silently;
--cdis the explicit decision. - Your home directory and blocked folders are refused as a working folder.
For a project shared with a team, put a .scopeshelf file into the repository and run plain scopeshelf shell in that folder:
{
"launcher": "my-name",
"shell": { "workingDirectory": "." }
}
Project settings in that file are layered over the launcher's own; a relative working folder is resolved against the project.
8. What the sandbox enforces, and what it does not
Every claim below is tested by a script in the repository (Scripts/check-sandbox-escapes.sh) that actually tries each escape from inside a sandboxed shell, 21 attempts as of 1.1.2; the full table of what is enforced where is in docs/sandbox-enforcement-map.md. Rules marked profile are part of every ScopeShelf sandbox and cannot be switched off from inside.
Enforced by the profile or the kernel
- Your home directory is invisible except for the grants. Blocked folders (
~/.ssh,~/.gnupg,~/.aws,~/.config/gcloud, keychains) stay blocked even inside a granted folder. - No loosening from inside. A nested
sandbox-execcan only restrict further; setuid programs such assudo,loginandcrontabcannot run at all. - No other app can be launched.
open -a Terminal script.commandoropen https://…is refused (Seatbeltlsopen): an app started that way would run without the sandbox. This also means links clicked inside a sandboxed app do not open your browser. - No signals to processes outside the sandbox. A sandboxed process can stop its own children, but not the ScopeShelf process that holds the domain filter, and not your other apps.
- No writes outside your home directory except temp folders (
/tmp,$TMPDIR) and explicit grants. Otherwise an agent could replace/opt/homebrew/bin/git, and your next unsandboxedgitwould run its code. Install tools from inside the sandbox into a granted folder. - ScopeShelf itself cannot be modified: the app bundle and the launcher store are write-protected even when a granted folder contains them.
- The sandbox survives ScopeShelf. The profile is attached to the process by the kernel. Kill the ScopeShelf process (even with SIGKILL from outside), quit the app, let it crash: every sandboxed process stays sandboxed, and a domain filter closes completely because its proxy is gone. A process inside cannot even send that signal (see above).
- Your ssh-agent is unreachable: the agent socket is blocked, so loaded keys cannot be used from inside.
- No unsandboxed helper of yours runs commands for it. A screen or tmux server, a Neovim or Emacs server, Docker or Shortcuts that already run outside the sandbox would execute commands without it. Their sockets, FIFOs and services are blocked; the shell gets its own temp folder instead of the shared one. tmux and screen started inside the sandbox keep working through private folders.
- Hardlinks and renames do not help. A protected or read-only file cannot be linked into a writable folder and edited there, and protected folders cannot be moved out from under their rules.
- With the network off, nothing listens either. A sandboxed process cannot open a port that an outside process could connect to.
- A
.scopeshelffile cannot widen its own sandbox. It may only grant folders inside the project; an agent that edits it gains nothing for the next start.
Enforced by macOS, not by the profile
- Apple events (
osascript -e 'tell application "Terminal" to do script …') are gated by the Automation permission in System Settings → Privacy & Security, and the sandboxed shell counts as part of the terminal it runs in. The profile cannot block them: we measured that Seatbelt'sappleevent-sendrule is accepted but has no effect. Do not grant your terminal permission to control other terminals if AI agents run inside it. - launchd refuses jobs from sandboxed processes (
launchctl submit,bootstrap,loadfail). That is launchd's behaviour; the test would turn red if it changed.
Not protected at all
- The terminal app itself. iTerm or Terminal.app runs with your full rights. Only the shell ScopeShelf started, and what runs inside it, is sandboxed.
- Other tabs and windows. A new tab in iTerm is a normal, unsandboxed shell. Keep AI agents in the window that shows the banner.
- Folders you granted as writable. Inside them an agent can still delete and change files. That is the point of granting them; keep the list short.
- Terminals ScopeShelf cannot drive. Alacritty, kitty, Warp and others get their sandboxed shell in a Terminal.app window, or you run
scopeshelf shellinside them yourself.
9. Error messages and what to do
| You see | Meaning and fix |
|---|---|
/Users/you/.zshenv:.:1: operation not permitted: /Users/you/.cargo/env | A startup file loads another file that is not granted. Edit launcher → step 4, Shell → tick it under Files loaded by those startup files. The banner names the file before the shell complains. |
… kann kein Arbeitsordner sein: eine Freigabe auf den Benutzerordner (oder darueber) wuerde nichts mehr sperren | "Cannot be a working folder": you picked ~ or a parent of it. Pick a project folder. |
… liegt im gesperrten Pfad … | "Lies in a blocked path": the working folder is under ~/.ssh, ~/.aws or another blocked folder. Blocks always win over grants. |
… ist im Profil nicht sichtbar, und es ist kein Arbeitsordner eingestellt | "Not visible in the profile, no working folder set": you ran scopeshelf shell in a folder outside the grants. Either cd to a granted folder, or pass --cd <folder> to make that folder the workspace. |
Arbeitsordner … ist im Profil nur lesbar | "Working folder is read-only in the profile": the folder is in the read-only grants, so the shell cannot write there. Change the grant in the launcher if it should. |
zsh: command not found: scopeshelf | The command line tool is not installed or its folder is not in your PATH. Settings → Command line tool shows the exact line to add. Inside a sandboxed shell the command is always available; this only concerns your normal shell. |
command not found: node (or claude, bun, cargo …) inside the shell | The tool lives in a folder under your home directory that is not granted. Edit launcher → step 4, Shell → tick it under Program folders. |
| Bare prompt, no aliases, no theme | Startup files are not ticked, or Login shell is off. Tick ~/.zshrc and friends. |
| Start shell does nothing, or Not authorized to send Apple events | ScopeShelf may not control your terminal yet. System Settings → Privacy & Security → Automation → ScopeShelf → allow iTerm / Terminal. |
Shell ist setuid und kann in der Sandbox nicht starten | The chosen shell program is itself setuid. Pick another under Program. |
Something missing on this page? Tell us, and we add it.