Workspace sandbox (macOS only)
A workspaced tab (agent -w / harness -w) confines its processes — the tab's shell, harness PTY, or ACP session, and anything they spawn — to the workspace directory using a kernel-enforced Seatbelt sandbox (sandbox-exec):
Writes are denied everywhere except the workspace and its private temp dir, plus a narrow set of harness-state carve-outs (
~/.claude/projects,~/.claude/session-env,~/.claude.json,~/.codex,~/.config/opencode, and a few package-manager caches) and two fixed OS-level paths a harness needs regardless of$HOME: the real per-user Darwin cache directory (a legacy Keychain subsystem locks a file there) and the harness CLI's own scratchpad directory (/private/tmp/claude-<uid>/, created before every tool call — without this carve-in, no Bash tool call works inside a workspaced tab at all). Never the whole~/.claude,~/.cache, or~/.npm— broad cache write access would let a sandboxed agent poison packages a non-sandboxed process later consumes.Reads of
$HOME's contents are denied by default and carved back in only for the write carve-outs above, plus~/.gitconfig,~/.config/gh/config.yml(gh's general settings — nothosts.yml, which stays denied),~/.claude/settings.json, and~/Library/Keychains(needed so a harness can look up its own OAuth credential — see Known limitations below). System paths (/usr, language runtimes, Homebrew) stay readable, as does a harness's own executable directory even when it lives under$HOME(nvm,~/.opencode/bin, …).A fixed list of secret paths is denied even inside a carve-in:
.ssh,.aws,.gnupg,.kube,.netrc, cloud CLI configs (ghhosts.yml,gcloud,azure,docker), credential files for Cargo/PyPI/Maven/Terraform, shell/REPL history files, and browser profile directories (Chrome, Firefox, Brave, Safari).Environment variables that could bypass the file-read denies above are stripped before spawn:
AWS_*,GITHUB_TOKEN,GH_TOKEN,NPM_TOKEN,DOCKER_*,KUBECONFIG, anything ending_SECRET/_PASSWORD,SSH_AUTH_SOCK,GPG_AGENT_INFO,GNUPGHOME,GIT_ASKPASS,GIT_CREDENTIAL_HELPER,KRB5CCNAME. LLM provider keys (ANTHROPIC_*,OPENAI_*,GEMINI_*/GOOGLE_*) are deliberately kept — the harnesses need their own credentials to function. AJANISSARY_NODEvariable is added, set to the janissary server's own Node binary path — so a script inside the sandbox (e.g. a project's own.claude/settings.jsonhook) can invoke a known-goodnodewithout depending onPATHresolution inside the sandboxed context. If a scoped GitHub token is configured (see "GitHub push/PR access" below),GH_TOKENis re-added after the strip, set to that token — the one deliberate exception, since it's a fresh value chosen for that workspace, not the ambient one just removed — andGH_CONFIG_DIRis pointed at an empty, workspace-private directory, sinceghreads~/.config/gh/hosts.ymlon every invocation regardless ofGH_TOKENand treats the sandbox's deny on that file as a fatal error rather than falling back; redirectingGH_CONFIG_DIRgives it a genuinely absenthosts.ymlinstead.Network is allowed by default; add
--offlineto a workspaced tab to deny it instead:harness claude -w --offline → workspaced harness tab with no network access
Practical consequences. No global installs, no reading sibling workspaces/other repos/dotfiles outside the carve-ins above. git commit/fetch/pull, npm install, builds, venvs, and harness login all work normally inside the workspace. The workspace's origin is HTTPS, pointed directly at GitHub — git push and gh (PR create/merge) work from inside the sandbox too, if a scoped GitHub token is configured (below); without one they fail, since .ssh is denied and SSH_AUTH_SOCK is stripped.
GitHub push/PR access
To let workspaced tabs git push and use gh (PR create/merge), create a fine-grained personal access token scoped to just this repo, with Contents: Read and write, Pull requests: Read and write, and Metadata: Read-only permissions — nothing broader. Save it to .janissary/github-token (already gitignored; janissary only reads this file, never writes it). Without this file, workspaces still work for local development, just not pushing/PR operations.
Configuration
Isolation is on by default; set "sandboxWorkspaces": false in .janissary/config.json to disable it (e.g. on a non-macOS host, or if it interferes with a particular harness). It also requires sandbox-exec to be present, which rules out non-macOS hosts. When a workspaced tab is created and isolation isn't actually active — the config key is off, or sandbox-exec is unavailable — a one-line notice is appended to the tab's transcript.
Known limitations
~/Library/Keychainsis readable, unlike the other secret paths above. Even "modern" Keychain Services calls fall through to a legacy implementation on macOS that reads the keychain database file directly rather than only talking tosecuritydover IPC — denying that read blocks every Keychain lookup a sandboxed process makes, including a harness's own OAuth credential, and it fails silently (the harness just reports "not logged in", with no permission error to explain why). The database itself stays encrypted and per-item ACL-enforced bysecuritydregardless of raw file readability, so this doesn't hand out plaintext secrets — but it is a materially larger read surface than the rest of the sandbox's design intends, kept as a deliberate trade-off.- The sandbox confines filesystem and network access; it does not sandbox CPU, memory, or other system resources, and a sandboxed process can still make outbound network requests (unless
--offlineis set). - macOS only — on other platforms,
-wstill creates the disposable git workspace, just without process confinement (a transcript notice says so).