Turn on the sandbox
In Settings → Agents → Agent sandbox, turn on Run agent commands in a sandbox. Commands that start after that run in the sandbox. On another operating system, or on a Mac without/usr/bin/sandbox-exec, the section shows why the sandbox is not available and you cannot turn it on. If the setting is already on there (for example in a config.json copied from a Mac), the section says so, and you can only turn it off: with the switch, or with Global or Off for a project that is On. Until then:
- On another operating system, commands run without the sandbox.
- On a Mac without
sandbox-exec, the commands that the sandbox covers fail. Xum does not run them without the sandbox that you turned on, unless you approve one command with Run outside once (see Approve one command outside the sandbox).
- Global: the project follows the switch.
- On: the sandbox is on for this project, also when the switch is off.
- Off: the sandbox is off for this project, also when the switch is on.
agentSandbox setting of a sub-project in config.json.
A multi-project workspace uses the strictest setting of its projects, because its commands can change each checkout. The sandbox is on when it is on for one of the projects. Commands can then use only the network mode, hosts, and paths that the sandbox of each project with the sandbox on allows. Host names match in any case. Paths match where they really are, so a folder that two projects name through different symlinks stays open. When the sandbox of one of the projects fails to start, commands of the workspace fail to start too.
Agent tools cannot change the sandbox setting. While the setting is on, globally or for one project, the agent’s config tool cannot write Xum’s config or providers at all, also on another operating system, where commands run without the sandbox.
What commands can write
Commands in the sandbox can write only to these folders:- The workspace and its scratch folder.
- Temp folders:
/private/tmpand$TMPDIR. The sandbox sets$TMPDIRto the system temp folder, also when a secret sets it to another folder. - Tool caches:
~/.npm,~/.cache,~/.cargo/registry,~/.cargo/git,~/go/pkg/mod,~/.gradle/caches,~/.m2/repository,~/Library/pnpm/store,~/.pnpm-store,~/Library/Caches/go-build,~/Library/Caches/pip,~/Library/Caches/Yarn,~/Library/Caches/ms-playwright,~/Library/Caches/node-gyp, and~/Library/Developer/Xcode/DerivedData. - For a worktree workspace, the
.gitfolder of the main checkout, so that commits work.
$TMPDIR that is a symlink stays closed, because a command could replace it with a link to another folder. Add the folder that it links to to extraWritePaths.
If Xum’s home folder (~/.xum, or XUM_ROOT) is inside a folder that commands can write, such as a temp folder, a tool cache or an extraWritePaths entry, commands in the sandbox fail to start, because a command could change Xum’s own settings there. They also fail to start when the workspace or another folder that they can write is inside ~/.config/git or a global skill or plugin folder, such as a plugin checkout in ~/.agents/plugins, or inside a folder that an entry of a global skill or plugin folder links to (~/.agents/plugins/demo linking to the workspace), because Xum runs the code there outside the sandbox. They also fail to start when the workspace, or an extraWritePaths entry, is reached through a link inside another folder that commands can write, such as a checkout in /tmp that a command replaced with a link to ~/.ssh, and when the scratch folder ($XUM_SCRATCH_DIR) is a link. The same applies to Xum’s home folder, the global git config (also where XDG_CONFIG_HOME, GIT_CONFIG_GLOBAL or GIT_CONFIG_SYSTEM moves it), ~/.bunfig.toml, ~/.ssh/config, the global instruction files, the npx, uv and Bun caches below (also where Xum’s environment or the project’s secrets move them), the credential and config files below where a variable names them (such as npm’s userconfig), the global skill and plugin folders and the links in them and in the global agent folder (~/.xum/agents), the git folders that a .git or commondir file names, the links in your home folder, the files that git and ssh config include, and the .git of the workspace: Xum, git, ssh, npx, uvx, bunx and your shell use them outside the sandbox through that link, which a command could replace. So a workspace whose .git is a symlink does not work in the sandbox. They also fail to start when Xum’s environment or the project’s secrets set one of the variables below that move the global git config, a tool cache or a credential file to a relative path, such as GIT_CONFIG_GLOBAL=my.gitconfig, because git, npm, uv and Bun resolve it from the folder they run in, and Xum runs its own git in the workspace. They also fail to start when the workspace, the scratch folder or another folder that they can read is inside one of the hidden credential paths below, such as Xum’s askpass folder, because the sandbox could not hide it there. They also fail to start when the project’s secrets set HOME to a folder other than your home folder, because git, npm and the MCP servers that Xum starts outside the sandbox with those secrets read their settings there. They also fail to start when the commondir file in the git folder of the workspace’s worktree names a folder other than the main checkout’s .git, because git outside the sandbox takes the config and hooks of the folder that it names.
Some files stay read-only, also inside the workspace, where they link to and in any letter case, because a command could use them to run code later outside the sandbox:
.git/hooks,.git/config,.git/config.worktree,.git/commondir,.git/modules(the git folders of submodules), and.gititself where it is not a folder: the.gitfile of a worktree, or a.gitthat does not exist yet, sogit initfails in the sandbox. In the.gitfolder of the main checkout, in the git folder that a.gitfile names (git init --separate-git-dir), and in the folder that thecommondirfile of either names, the same paths, andconfig.worktree,modulesandcommondirin the folder of each worktree. Also the files that the hooks in thesehooksfolders link to, such asscripts/post-checkoutin the workspace for a.git/hooks/post-checkoutlink..xum/mcp.jsonc,.mux/mcp.jsonc,.xum/mcp.local.jsonc,.mux/mcp.local.jsonc,.xum/mcp.local.json,.mux/mcp.local.json,.mcp.json,.xum/plugins,.mux/plugins, and.agents/plugins..xum/tool_envand.mux/tool_env, which bash runs before each command in a trusted project, also before a command that you let run outside the sandbox.~/.npm/_npx(or wherenpm_config_cachemoves the npm cache), the uv cache (~/.cache/uv, or whereXDG_CACHE_HOMEorUV_CACHE_DIRmoves it), the Bun install and transpiler caches (~/.bun/install/cacheand~/Library/Caches/bun, or whereBUN_INSTALL_CACHE_DIR,BUN_INSTALL,BUN_RUNTIME_TRANSPILER_CACHE_PATHorXDG_CACHE_HOMEmoves them), and thebunx-<uid>-*and.bunx-<uid>folders in the temp folders.npx,uvxandbunxkeep what they run there, also for MCP servers that start outside the sandbox. Commands in the sandbox can still run what is there. uv and Bun in the sandbox use caches of their own in$TMPDIR..vscode,.idea,.envrc, and.gitmodules..devcontainer.jsonand.devcontainer, becausedevcontainer upruns theirinitializeCommandon your Mac.- Where links in your home folder point into a folder that commands can write (a dotfiles checkout): the dot entries of the home folder (such as
~/.gitconfig,~/.zshrcor~/.npmrc), the entries of~/.config,~/.ssh(such as~/.ssh/config),~/.claudeand Xum’s home folder (such as a globalCLAUDE.mdorAGENTS.md), and the global skill and plugin folders, because git, ssh,npx,bunxand your shell outside the sandbox run what they name, and Xum reads the global instruction files into its prompt. A link to the whole folder that commands can write, or to a folder above it, does not make that folder read-only. - Where an entry of a global skill, plugin or agent folder links into a folder that commands can write (a plugin linked to its checkout), the folder that it links to. Links deeper inside the entry are not followed.
- The global and system git config, also where Xum’s environment or the project’s secrets move them (
XDG_CONFIG_HOME,GIT_CONFIG_GLOBALorGIT_CONFIG_SYSTEM), where they are in a folder that commands can write. The system config is/etc/gitconfig, theetc/gitconfigof Homebrew or MacPorts, or the defaults that Apple’s git ships with in the Command Line Tools or in/Applications/Xcode.app; a git with its system config elsewhere is not covered. ~/.ssh/config, where it is in a folder that commands can write (anextraWritePathsentry for~/.ssh), because ssh runs itsProxyCommandfor Xum’s owngit fetchoutside the sandbox.- The global instruction files (
~/.claude/CLAUDE.md, andAGENTS.md,AGENT.md,CLAUDE.mdandAGENTS.local.mdin Xum’s home folder), where they are in a folder that commands can write, because Xum reads them into the prompt of every workspace, also one without the sandbox. - The files that git config includes (
include.pathandincludeIfentries, in the global and system git config, in the config of the repository that the workspace is in, also when the workspace is a folder inside it, and of its submodules, and in the files they include), and the files that~/.ssh/configand the system/etc/ssh/ssh_configinclude (Include), where they are in a folder that commands can write. For anIncludewith a wildcard in its last name, every name in that folder that starts like the pattern stays read-only, and so does where a link among them points. Commands fail to start when an include is in a form the sandbox does not read: a partly quoted or escaped git include path,~user/or%(prefix)/, anIncludewith a token, a variable or a wildcard in a folder name, or anIncludeargument with a backslash or quoted in part. They also fail to start when one of these config files is larger than 1 MiB.
.xum/plugins or .git/hooks, are not checked for hard links. A command in the sandbox cannot make a hard link to them, but a program outside the sandbox can. For example, pnpm with package-import-method=hardlink links the files in a plugin’s node_modules to its store, which commands can write, so a command can change them there.
What commands can read
Commands in the sandbox can read:- The workspace and the folders they can write.
- System files and folders outside
/Users,/Volumesand your home folder. - Toolchains and tool settings in your home folder:
~/.gitconfig,~/.config/git,~/.bun,~/.cargo,~/.rustup,~/go,~/Library/Application Support/go,~/.nvm,~/.volta,~/.pyenv,~/.asdf,~/.local/share/mise,~/.local/share/uv, and~/.sdkman. A link in a folder that commands can write is not followed from one of them, because a command could point it at~/.ssh. - The global git config, also where Xum’s environment or the project’s secrets move it.
- The global skill and plugin folders.
- The folders in your
PATHthat are in your home folder. Only the folder itself opens: if a tool in it needs files next to it, add them toextraReadPaths. APATHfolder that a symlink in a writable folder links to stays closed. - For a tool hook, the payload files that
XUM_TOOL_INPUT_PATHandXUM_TOOL_RESULT_PATHname. - For a Cursor plugin hook, the chat log that
transcript_pathnames. The hook starts in the workspace, so the plugin folder stays read-only, and then changes to the plugin folder.
/Users, /Volumes, your home folder (also when it is not in /Users) and Xum’s home folder (also when XUM_ROOT moves it outside your home folder) is hidden. Some credential files stay hidden and read-only, also inside folders that are open and when you add them to extraReadPaths or extraWritePaths. Where a variable moves one, the sandbox reads it from Xum’s environment and from the project’s secrets:
~/.cargo/credentialsand~/.cargo/credentials.toml(or inCARGO_HOME).~/.config/git/credentials(or whereXDG_CONFIG_HOMEmoves it).~/.cache/huggingface/token, andstored_tokensnext to it, wherehf auth loginkeeps every token it saved (or whereXDG_CACHE_HOME,HF_HOMEorHF_TOKEN_PATHmoves them).~/.local/share/uv/credentials(or whereXDG_DATA_HOMEorUV_CREDENTIALS_DIRmoves it), whereuv auth loginkeeps its tokens, so uv in the sandbox cannot use them.~/Library/Application Support/go/env(or whereGOENVmoves it), wherego env -wkeeps its settings, which can hold a password inGOPROXY. Go in the sandbox runs without them, so set what a build needs, such asGOPRIVATEorGOFLAGS, in the environment instead.- The npm user and global config where a variable moves them, because they can hold registry tokens:
npm_config_userconfig,npm_config_globalconfig, andnpm_config_prefixorPREFIX, which move the global config to<prefix>/etc/npmrc. npm readsnpm_config_in any case. Without them,~/.npmrcis only closed (see below). The global config of a Node in an open folder, such as~/.nvm/versions/node/<version>/etc/npmrc, stays open, so keep registry tokens in~/.npmrc. - The pip config that
PIP_CONFIG_FILEnames. pip in the sandbox runs without it. - The
gradle.propertiesin the folder thatGRADLE_USER_HOMEnames, because it can hold repository credentials. Gradle in the sandbox runs without it. - The
config.jsonin the folder thatDOCKER_CONFIGnames, because it can hold registry logins and proxy passwords.~/.dockeris not open either. - The AWS files that
AWS_SHARED_CREDENTIALS_FILEandAWS_CONFIG_FILEname (also with a leading~/), the key files thatGOOGLE_APPLICATION_CREDENTIALSandAZURE_CLIENT_CERTIFICATE_PATHname, the folders thatCLOUDSDK_CONFIGandAZURE_CONFIG_DIRname, thehosts.ymlin the folder thatGH_CONFIG_DIRnames, and each file thatKUBECONFIGlists, because they hold cloud keys and tokens. Their default folders in your home folder are not open either. - The
.netrcthatNETRCnames, because Python and Go read logins from it. - The files where more tools keep tokens, where a variable moves them: the Terraform CLI config that
TF_CLI_CONFIG_FILEorTERRAFORM_CONFIGnames,credentials.jsoninPULUMI_HOME, the configs thatRCLONE_CONFIG,BOTO_CONFIG,OCI_CLI_CONFIG_FILE,DATABRICKS_CONFIG_FILE,HUB_CONFIGandHELM_REGISTRY_CONFIGname,auth.jsoninCOMPOSER_HOME,config.ymlinGLAB_CONFIG_DIR,kaggle.jsoninKAGGLE_CONFIG_DIR, andkey.peminDOCKER_CERT_PATH. - The file that a variable names when its name says it holds a secret: the name ends in
TOKEN,CRED,CREDS,CREDENTIAL,CREDENTIALS,PASS,PASSWORD,SECRET,SECRETS,KEYorAUTH, then_FILE,FILE,_PATHorPATH, then an optional_OVERRIDE. Examples areAWS_WEB_IDENTITY_TOKEN_FILE,CLOUDSDK_AUTH_ACCESS_TOKEN_FILE,CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE,AZURE_FEDERATED_TOKEN_FILE,PGPASSFILE,SOPS_AGE_KEY_FILEandPOSTGRES_PASSWORD_FILE. Only a value that is an absolute path or starts with~/counts. - Xum’s
server.lock,providers.jsonc,secrets.json,mcp-oauth.json, andconfig.json, and itsaskpassfolder, where your answers to SSH prompts pass.xum runandxum workflowkeep their copy ofconfig.json,providers.jsoncandsecrets.jsonin a temp folder, and that copy is hidden too, with the files whose names start with one of these names and a dot, such as the temp file that Xum writes before it replaces one.
~/.npmrc, ~/.bunfig.toml, ~/.pypirc, ~/.gradle/gradle.properties, and ~/.m2/settings.xml. If a build needs one of them, add it to extraReadPaths (see Open more paths).
Xum reads the instruction files of the workspace, such as AGENTS.md, its agent definitions in .xum/agents and its skills in .xum/skills or .agents/skills as a command in the sandbox would, with the project’s secrets, because a command could replace one with a link to a key or to a file that a secret names. An instruction file that links to a hidden file loads as empty. An agent definition or skill that links to one does not load, so the agent or skill of that name in Xum’s home folder, or the built-in one, loads in its place. agent_skill_read_file does not read a hidden file from a workspace skill, agent_skill_write and agent_skill_delete change a file of a workspace skill only where a command in the sandbox could, and mux_agents_read and mux_agents_write read and write the workspace’s AGENTS.md only where a command in the sandbox could. workflow_run runs a workflow script of the workspace or of a workspace skill, also one that another workflow starts, only where a command in the sandbox could read it. To load a file that links into your home folder, add its target to extraReadPaths. When Xum proposes the plan of plan mode, it reads the plan file the same way. Commands cannot change the plan file, because it is in Xum’s home folder.
Unix sockets are closed in all network modes, for example the sockets of Docker, ssh-agent, 1Password, and gpg-agent. The macOS keychain is closed too, and open cannot start an app, which would run outside the sandbox.
Network
Commands in the sandbox reach the network through a local proxy. In Network, select one mode:- Allowed hosts only (the default): commands can reach only the hosts in the list. The default list has 16 package registries and git hosts:
registry.npmjs.org,registry.yarnpkg.com,pypi.org,files.pythonhosted.org,crates.io,index.crates.io,static.crates.io,proxy.golang.org,sum.golang.org,github.com,api.github.com,codeload.github.com,objects.githubusercontent.com,raw.githubusercontent.com,gitlab.com, andbitbucket.org. - Any host: commands can reach all hosts.
- Loopback only: commands can reach only
localhost.
0.0.0.0 or ::. The macOS sandbox cannot limit a listener to loopback: it treats a listener on every interface as a loopback listener.
Open more paths
To let commands write or read more paths, edit~/.xum/config.json:
~/. A project can have its own agentSandbox with the same fields, in its entry in projects. Each field that the project sets replaces the global field. For example, a project extraReadPaths replaces the global extraReadPaths, it does not add to it. A repository cannot set the sandbox. The agent’s config tool cannot change agentSandbox, and while the sandbox is on, it cannot write Xum’s config or providers at all. In a system workspace that the sandbox covers, the agent’s skill and AGENTS.md tools cannot change the global skills or Xum’s global AGENTS.md, because every workspace loads them. In any workspace that the sandbox covers, the memory tool cannot change global memory, because every workspace with memory loads its index. For the same reasons, the refinement rollback tool cannot undo a change to global memory or a global skill in a workspace that the sandbox covers.
Xum ignores a field that is not valid and keeps the valid fields, so a broken field next to "enabled": true does not turn the sandbox off. An agentSandbox value that is not an object is ignored as a whole. Xum shows no message for this, and the next save from Settings writes the value without the broken fields.
Approve one command outside the sandbox
When the sandbox blocks a command, the bash card shows a Sandbox badge and a line that names what the sandbox blocked. The agent can then ask to run the command once outside the sandbox, with a reason. The request shows as a card with the command and the reason. Click Run outside once to run that one call without the sandbox, or Deny to refuse it. The keyboard shortcuts answer the newest card (see Keyboard shortcuts). Each approval applies to one call. Xum never remembers it. Headlessxum run, xum workflow, ACP clients, and sub-agents refuse such requests at once. A sub-agent cannot ask you anything, but the agent that started it can ask to run the command.
An approved call starts without BASH_ENV and without the bash functions that Xum’s environment exports (BASH_FUNC_<name>%%), as do MCP servers and the commands that Xum runs for itself while the sandbox is on for any project, because a sandboxed command can change the file that BASH_ENV names, and bash runs an exported function in place of the program it names, which can run a file in the workspace. An approved call runs /bin/bash, which macOS protects, not the first bash on your PATH, so a script that needs a newer bash can fail there; an approved background call also starts /usr/bin/nohup. The scripts that Xum runs for itself also use /bin/bash and the folders that macOS protects (/usr/bin, /bin, /usr/sbin and /sbin) in place of your PATH, because a folder in your PATH, such as an activated .venv/bin, can be in the workspace.
Known limits
- Programs with a sandbox of their own, such as Chrome, Electron, Playwright, Codex, and Claude Code, cannot start it inside the agent sandbox. Start them with their no-sandbox flag. Codex and Claude Code also need their login folders in
extraReadPaths. - Commands can read the global git config (
~/.gitconfigand~/.config/git), so that git keeps your name, settings, and aliases. A token in it, for example inhttp.extraHeaderor in a remote URL, is readable to commands, and they can use it with an allowed host. Keep git tokens in a credential helper, such as the macOS keychain, which the sandbox closes. git pushover SSH fails, because thessh-agentsocket and the SSH keys are closed.ghwith a stored login fails, because the keychain is closed. Approve the one push outside the sandbox.- A commit or
git submodule updatethat changes the git folder of a submodule fails, because that folder is read-only. Approve the command outside the sandbox. Xum creates worktrees outside the sandbox:git worktree addand the submodule checkout after it run with your access, as they do with the sandbox off. - In a repository whose git folder is outside the workspace (
git init --separate-git-dir), git cannot write the index, objects or refs, because commands can write.gitfiles, so the sandbox does not open the folder that one names. Add that git folder toextraWritePaths: its hooks and config stay read-only. A linked worktree needs no entry. - An instruction file such as
AGENTS.md, an agent definition or a project skill that links to a file in the workspace’s scratch folder ($XUM_SCRATCH_DIR) loads as empty, because Xum checks these files without the scratch folder, also outside a turn. Commands and file tools can still read the file. - File tools check the real path of a file and then open it. A sandboxed command that swaps a symlink between the check and the open can make a file tool read or edit a file that the sandbox hides.
- The VS Code extension shows no approval card yet. A request to run a command outside the sandbox waits there until you stop the agent.
- Project secrets and the bash AI proxy key are still in the environment of agent commands.
- A file in an open folder, such as a toolchain folder or the workspace, is readable to commands, also when a variable names it as the config of a tool. The sandbox hides only the credential files listed in What commands can read. Some tools fail when they cannot read the config that a variable names (uv with
UV_CONFIG_FILE), so the sandbox leaves those files as open as their folder. Keep files with tokens out of the open folders. - Commands can read the command line and the starting environment of your other programs that are not part of macOS, for example
nodeorpythonprocesses, or Xum when you startxum serverfrom a shell. macOS gives the sandbox no way to hide them. A secret in them is readable to commands: a key that your shell profile exports, or the token ofxum server --auth-tokenorXUM_SERVER_AUTH_TOKEN, with which a command can call Xum’s API and turn the sandbox off. With the sandbox on, startxum serverwithout a token on its command line or in its environment, so it makes its own. - The sandbox does not cover terminals that you open, MCP servers (they start outside the sandbox), and the Browser tab.
- A project’s own
networkandallowedHostsare not a boundary between projects. While a sandboxed command of another project runs, a command can read that command’s proxy credential from its process environment and reach the hosts that the other project allows. Settings sets one network mode and host list for all projects, so this matters only when a project inconfig.jsonhas its ownnetworkorallowedHosts. - In Allowed hosts only mode, a background process that
xum run --keep-background-processesleaves running can reach only loopback afterxum runexits, because the proxy to the allowed hosts runs inxum run. - With
xum server --no-auth, a command in the sandbox can call Xum’s API on loopback like any other program on your Mac, so it can turn the sandbox off or change any other setting. Do not use--no-authwith the sandbox. - Commands can change the other files in the workspace. When you, your editor, or a Dev Container later runs one of them outside the sandbox, for example a script that
initializeCommandnames or a Dev Container config in another folder, the change runs with your full access. This includes the git hooks and config of a repository nested in the workspace, such asvendor/tool/.git: only the.gitof the workspace itself stays read-only, so the next git command in the nested repository runs what they name. It also includes a hooks folder or program in the workspace that your git config or Xum’s environment names, for example withcore.hooksPath,core.fsmonitororGIT_SSH_COMMAND, and a file in the workspace that a link deeper inside a global skill or plugin entry points to, such as~/.agents/skills/demo/scripts/run.sh. - Tool caches, temp folders and the workspace are writable, so a command can change a package that a program outside the sandbox runs later, for example a build in your terminal or editor, also in another project that uses the same cached package (a crate in
~/.cargo/registry, a Go module, a Maven or Gradle artifact, or a package in the pnpm store), or an MCP server thatnpxorbunxfinds in thenode_modulesfolder of the workspace or gets from a registry that a.npmrc,bunfig.tomloruv.tomlin the workspace names. MCP servers and Xum’s own git, such as forgit worktree addand the submodule checkout after it, start with yourPATH, because they neednpx,uvx,nodeorgit-lfsfrom it. A command can change a program in a folder of yourPATHthat it can write, such as an activated.venv/bin, and an MCP server whose command names that program, or Xum’s own git, runs the change outside the sandbox. - Xum keeps the output and exit status of each background process in
/private/tmp/mux-bashes, which commands can write. A command can change what another background process printed, also one of another workspace, or mark one that outlived a restart of Xum as ended, so that Xum archives its workspace while it still runs. - With Bun 1.4.3 or older,
bunxfails in the sandbox, because it keeps what it runs in/private/tmp, where that stays read-only. Update Bun, or approve the call outside the sandbox.