macOS Sandbox
macOS lacks Flatpak but has an equivalent, sandbox-exec, where the same
.tools-only layout helps just as much.
The sandbox limits VSCodium's read and write access to the
~/Documents/repos/wekan directory. In that WeKan repository the .tools
subdirectory, listed in .gitignore, holds all related work: the companion
repositories, the .tools/tmp temporary directory, the .tools/log log files,
Meteor, Node.js and the caches.
It is the macOS counterpart of the Linux Flatpak sandbox in ../vscodium; the Windows one is in ../Windows.
Quick start
cd ~/Documents/repos/wekan
docs/Security/Sandboxes/macOS/sandbox.sh --check # set up, then show what is allowed and refused
docs/Security/Sandboxes/macOS/sandbox.sh # start VSCodium in the sandbox
sandbox.sh installs VSCodium with Homebrew if it is missing, writes the
sandbox profile, and writes a Finder launcher, .tools/sandbox/WeKan VSCodium.command. Double-click it or drag it to the Dock to start the
sandboxed VSCodium. The first start asks whether VSCodium may access the
Documents folder: allow it, because the checkout is there.
Install WeKan's tools into .tools once, from a normal terminal outside
the sandbox (./build.sh, option 1). Inside the sandbox they are already on
PATH.
Claude Code in the sandbox
Claude Code keeps its login in the macOS Keychain, which the sandbox cannot reach, so signing in inside the sandbox does not stick: macOS asks for a keychain to store "user" in, and Claude Code shows its login screen again. Give it a long-lived token instead, once, from a normal terminal outside the sandbox:
claude setup-token # sign in in the browser; prints a token valid for a year
docs/Security/Sandboxes/macOS/sandbox.sh --claude-token # copy the token first; saved to .tools/secrets/claude-oauth-token
The token is printed over several lines: copy all of them. --claude-token
reads it from the clipboard, joins the lines, and refuses a token that is
cut short, which Claude Code would answer with "401 Invalid bearer token".
In zsh, paste the commands without the # comments.
Then restart the sandboxed VSCodium. sandbox.sh passes the token as
CLAUDE_CODE_OAUTH_TOKEN, marks Claude Code's onboarding done in
.tools/home/.claude.json, and sets "claudeCode.disableLoginPrompt": true
in VSCodium's settings, so neither the extension nor the claude command in
the terminal asks for a login. Everything in the sandbox can read the token,
as it could read any login made there; revoke it at claude.ai under
Settings if it leaks, and make a new one when it expires.
What the sandbox allows and refuses
sandbox.sh --check runs these checks inside the sandbox and prints the
result. They were verified on macOS 27:
| Refused | the home directory: Desktop, Documents outside the checkout, Downloads, ~/Library, browser profiles |
| Refused | ~/.ssh, ~/.gitconfig, the login session's ssh-agent socket |
| Refused | other users' homes, /Users/Shared, other disks under /Volumes |
| Allowed | read and write ~/Documents/repos/wekan, including .tools/ |
| Allowed | the system (/System, /usr, /Applications, /opt/homebrew), read only where macOS makes it so |
| Allowed | the network, as in the Flatpak (--share=network): git, npm, Meteor and tests need it |
How it works
sandbox-exec runs a program under a Seatbelt profile: the same
kernel-enforced mechanism macOS uses for App Store apps and its own daemons.
The profile is written by sandbox.sh to .tools/sandbox/vscodium-macos.sb
with the checkout's real path:
(version 1)
(allow default)
(deny file-read* file-write* (subpath "/Users") (subpath "/Volumes"))
(allow file-read-metadata (literal "/") (literal "/Users") (literal "/Users/<you>") ...)
(allow file-read* file-write* (subpath "/Users/<you>/Documents/repos/wekan"))
(deny file-read* file-write* network-outbound
(regex #"^/private/tmp/com\.apple\.launchd\.[^/]+/Listeners$"))
- Every process VSCodium starts inherits it: the editor, its helper
processes, extensions, the integrated terminal and everything run in it
(
build.sh,meteor,node,git, Claude Code). A command typed in the sandboxed terminal cannot leave the sandbox. - A home of its own:
HOMEis.tools/home, as Flatpak gives an app its own home. Shell history, npm and git configuration and Claude Code's settings land there, not in the real home. - VSCodium's own data: settings and extensions are kept in
.tools/vscodium/user-dataand.tools/vscodium/extensions, given with--user-data-dirand--extensions-dir. They are not in~/Libraryor~/.vscode-oss, which the sandbox cannot reach. - Started in the checkout:
sandbox.shchanges to the checkout before it starts anything. Finder opens the launcher in the home directory, which the sandbox refuses, and VSCodium then stopped at once with "EPERM: process.cwd failed with error operation not permitted, uv_cwd". - Temporary files:
TMPDIR,TMPandTEMPare.tools/tmp, as for every WeKan script (releases/tools-env.sh). - A clean environment: VSCodium starts with
env -i. That dropsSSH_AUTH_SOCK, andELECTRON_RUN_AS_NODE, which an editor's terminal sets and which makes VSCodium start as plain Node.js ("bad option: --user-data-dir"). - The commit author: inside the sandbox
~/.gitconfigcannot be read, sosandbox.shcopiesuser.nameanduser.emailinto the checkout's own.git/configonce, from outside the sandbox. CLAUDE.md asks for exactly that identity in the checkout anyway.
Trade-offs, and why
--no-sandboxfor VSCodium. Chromium, which VSCodium is built on, sandboxes its own helper processes with the same Seatbelt mechanism. macOS does not let a process that is already sandboxed start a sandbox of its own: every helper then failed with "sandbox initialization failed: Operation not permitted", and VSCodium exited. So VSCodium runs with Chromium's inner sandbox off, and the outer one confines all of it. The helpers are no longer isolated from each other, but none of them can reach anything outside the checkout. That is the property this sandbox exists for. The Flatpak build of VSCodium makes the same trade with zypak.- No Keychain:
--use-mock-keychain. The login keychain is in~/Library/Keychains, which the sandbox refuses. Without this switch VSCodium's secret storage (ElectronsafeStorage) asks for a keychain to keep "VSCodium Key" in, with the dialog "A keychain cannot be found to store 'VSCodium Key'" (in Finnish "Avainnippua ei löydy"). Never press Reset To Defaults (Palauta oletuksiin) there: it would replace the login keychain with a new, empty one. Press Cancel (Kumoa) and start VSCodium again with the updatedsandbox.sh. With the switch, Chromium uses a built-in mock keychain with a fixed, publicly known key and does not touch the real Keychain (Claude Code's own login also lives in the Keychain; see "Claude Code in the sandbox" above); it is the macOS equivalent of--password-store=basic, which works on Linux only. The price: what extensions store as secrets (sign-ins, tokens) in.tools/vscodium/user-datais only obfuscated, readable by anyone who can read the checkout. Keep long-lived credentials out of the sandbox. Opening~/Library/Keychainsto the sandbox instead would be worse. sandbox-execis marked deprecated in its man page, but it has been present and working in every macOS release since 10.5, including macOS 27, where this was tested. Apple uses the same profiles for its own services. If it is ever removed, the alternative is a second macOS user account that owns only the checkout. That is stronger but less convenient.- The system temporary directory. macOS frameworks keep a few files in
the per-user
/var/foldersdirectory whateverTMPDIRsays (mktempwithout a template does too), so it is not refused. WeKan's own scripts never write there: each names.tools/tmp. - No ssh keys inside. The sandbox cannot read
~/.sshor use the agent, as--nosocket=ssh-authrefuses it in the Flatpak. Pushing is done from a terminal outside the sandbox. That is also the rule for AI assistants in CLAUDE.md: they commit, and a human pushes. - The network is open, as in the Flatpak. Restricting it would need a proxy and would break git, npm, Meteor and the tests that download browsers and databases.
Updating and removing
- Update VSCodium: run
brew upgrade --cask vscodiumoutside the sandbox. - Change the profile: edit
sandbox.shand run it again. The.sbfile is regenerated and says so in its first line. - Remove everything: delete
.tools/sandbox,.tools/homeand.tools/vscodium. Nothing outside the checkout was changed, except VSCodium in/Applicationsif Homebrew installed it.