Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

xcbox logo

xcbox

Run a coding agent in a sandbox scoped to your Xcode/Swift project.
The agent sees only your repo — yet it builds, tests, and runs on the host, and commits as you.


xcbox drops Claude Code or Codex into a Linux container with only your project's git repository mounted — nothing else of your machine is visible. The agent still builds and tests the real app on the host via XcodeBuildMCP, and commits & pushes with your git identity over your forwarded SSH agent — your keys never enter the container.

Why xcbox

Let the agent run amok — build, test, commit — without babysitting. The safe way is a Linux sandbox scoped to your repo, so a mistake stays contained there and never reaches the rest of your host.

The catch: Xcode doesn't run on Linux. xcodebuild, the simulators, the SDKs are all macOS-only, so a plain container can't compile an iOS or macOS app.

xcbox squares that circle: the agent stays sandboxed in Linux and sees only your repo, while real builds are delegated to the host over a gateway — blast-radius protection and full-fidelity Apple builds.

Highlights

  • One command. Run xcbox from an Xcode/Swift project or a repo containing one.
  • Project-scoped sandbox. The agent's filesystem is limited to your repo; the rest of the host stays invisible.
  • Real host builds. xcodebuild and simulators run on macOS through XcodeBuildMCP — full fidelity, no toolchain shipped in the container.
  • Commits as you. Uses your host Git identity and forwarded SSH agent, including SSH commit signing; private keys stay on the host.
  • Every Apple platform, plus Swift packages — because XcodeBuildMCP drives them all.

Install

Install xcbox and its Node.js and Apple container dependencies with Homebrew:

brew install Bunn/tap/xcbox

Then configure Apple container's localhost bridge once and verify your host:

sudo container system dns create host.container.internal --localhost 203.0.113.113
xcbox doctor

To install directly from source instead, clone the repository and put xcbox on your PATH (the installer symlinks bin/xcbox into a bin directory already on your PATH):

git clone git@github.com:Bunn/xcbox.git && cd xcbox
./install.sh                 # auto-picks a writable dir on PATH

./install.sh /usr/local/bin installs into a specific dir; ./install.sh --uninstall removes it. The symlink resolves back to the repo, so git pull updates the installed command. Prefer not to install? Just run bin/xcbox directly.

Quickstart

cd ~/YourApp
xcbox                        # brings up the sandbox and drops you into a shell

The first time you open a project, choose its agent:

Choose the coding agent for this project:
  1) Claude Code
  2) Codex
Agent [1-2]:

xcbox remembers that choice for this project. To switch it later (and remember the new choice), run xcbox --agent codex or xcbox --agent claude.

Inside the box, start your agent and point it at the project:

claude                       # when Claude Code is selected
# or
codex                        # when Codex is selected
> Build and test this app, then commit and push.

Sign in only if prompted. Each agent's login and sessions persist in this project's isolated home.

Run xcbox doctor first if you want to check prerequisites. Full walkthrough: docs/xcbox-quickstart.md.

How it works

HOST (macOS)                                CONTAINER (Linux · repo-only)
  XcodeBuildMCP behind a stateful             your agent (e.g. Claude Code)
  loopback gateway on :8765  ◄────────────────  host.container.internal:8765
  Xcode toolchain (xcodebuild / simctl)       sees ONLY your git repo + its own home
  isolated per-project agent home             login is seeded; mutable state stays separate
  your git identity + forwarded SSH agent     commits as you; keys stay on the host

xcbox resolves the nearest Xcode/Swift project, or a unique nested project when run from the repository root. It mounts the git repository root (so .git is present and commit/push work even when the Xcode project lives in a subdirectory) and drops you into the resolved project directory. The build server is registered with the agent at user scope; the agent calls real xcodebuild on the host and the results stream back over the gateway.

Requirements

Apple Silicon · macOS 26+ · Xcode 26+ · Node.js 20+ · Apple container CLI · a global git identity · an SSH agent with a key loaded · Apple container's localhost DNS bridge.

sudo container system dns create host.container.internal --localhost 203.0.113.113
xcbox doctor                 # checks all of the above, including the bridge

The DNS bridge lets boxes reach a host service bound only to 127.0.0.1. Apple container may remove the rule after a restart; xcbox doctor and xcbox up report the command to recreate it. On first use, xcbox up installs the exact MCP SDK and XcodeBuildMCP versions recorded in package-lock.json. The HTTP bridge is part of xcbox itself; later starts use the locked local runtime and do not contact npm unless the lock changes.

Commands

Command Description
xcbox · xcbox up enter the sandbox; asks for an agent on first use
xcbox --agent claude|codex select, provision, and remember this project's agent
xcbox --update update this project's selected agent to its latest release
xcbox list list every box, project path, state, home status, and retained home
xcbox status verify host + box gateway, real MCP, agent, and forwarded SSH state
xcbox stop stop this project's box (--gateway also stops the gateway)
xcbox logs tail the gateway log (-f follows; --lines N controls history)
xcbox rm remove this project's box (keeps ~/.xcbox-home)
xcbox reset preview removal of this project's box, retained home, and saved agent; --yes applies it
xcbox prune preview stopped artifacts for deleted projects; --yes removes them
xcbox doctor check host prerequisites

Each box gets an independent home under ~/.xcbox-home/boxes/<box-name>. Its Claude and Codex logins, sessions, installed packages, npm cache, and Git configuration persist across runs without being writable by other boxes. Both agents can coexist when you switch. A new home copies the freshest existing Claude login and user preferences once; Codex uses its normal first-run sign-in. It does not copy project history or caches. xcbox rm retains that home and the last agent selection.

xcbox list works from any directory. It includes running and stopped containers, warns about legacy/shared or mismatched /root mounts, and shows retained per-project homes after xcbox rm.

xcbox logs -f follows new gateway output until interrupted; Ctrl-C stops only the follower. Use xcbox logs --lines N (or -n N) to choose how much existing history is printed first.

Cleanup is deliberately opt-in. xcbox reset and xcbox prune are dry runs unless --yes is provided. Reset never touches the source repository or shared gateway. Prune removes only stopped containers, isolated homes, and metadata whose recorded project path no longer exists; running boxes are reported and skipped.

Boxes created by an older version may still mount all of ~/.xcbox-home as /root. xcbox refuses to start those with shared state and asks you to stop/remove/recreate the container; the old home is left untouched and used only to seed login/preferences into the replacement.

For a running box, xcbox status checks the complete operational path: an in-container gateway health request, a bounded stateful MCP tools/list session, and ssh-add -l through the forwarded socket. If the host requires SSH-signed commits, it also checks that the selected public key matches an identity in the forwarded agent. Failures include the relevant restart, DNS bridge, log, SSH-agent, or box-recreation command.

FAQ

Can I have multiple xcboxes running in parallel?

Yes. Each project gets a box named from a readable directory slug plus a short hash of its canonical full path, such as xcbox-myapp-a1b2c3d4. Different projects with the same folder name therefore remain independent, while opening the same project through a symlink reuses its box. All boxes share the single build gateway on :8765, which multiplexes their xcodebuild calls.

If a repository contains multiple Xcode projects, run xcbox inside the intended project directory or select it explicitly, for example PROJECT=apps/MyApp xcbox. xcbox lists the candidates instead of choosing one arbitrarily.

Boxes created by older xcbox versions used only the directory basename. When one is found, xcbox refuses to guess its ownership and prints explicit inspect/remove/recreate instructions; it never stops or removes that legacy box automatically.

Can I use Claude Code and Codex?

Yes. On first use, bare xcbox asks which one to install and remembers the answer per project. xcbox --agent claude and xcbox --agent codex switch explicitly. xcbox installs an exact tested package version, registers the same ios-build HTTP MCP for the chosen CLI, and leaves the other agent's state intact. Use xcbox --update when you deliberately want the selected agent's latest release.

For scripts, use XCBOX_AGENT=claude|codex or --agent; xcbox refuses to guess when there is no saved choice and stdin is non-interactive. Package overrides remain available as XCBOX_CLAUDE_INSTALL, XCBOX_CODEX_INSTALL, or the shared XCBOX_AGENT_INSTALL escape hatch. Other agents can still be installed manually in the plain Linux box and pointed at http://host.container.internal:8765/mcp, but only Claude Code and Codex are provisioned automatically.

Is my host machine 100% protected?

No. xcbox is a blast-radius tool, not a security boundary against a malicious agent. It limits what the agent can see (only your repo), but builds still execute your project's real build scripts on the host via xcodebuild, the container keeps network access, and the gateway on :8765 has no authentication. The gateway is loopback-only, but any local process or container using the configured localhost bridge can reach it. Treat xcbox as protection against mistakes, not against hostile code. See Security model for the full picture.

Do my SSH keys or credentials end up in the container?

Private keys do not. Only the SSH agent socket is forwarded (--ssh), so the box can authenticate and sign without private key material leaving the host. Your Git identity is copied in. When the host uses SSH commit signing, xcbox also copies only the selected public key into that project's isolated home and mirrors gpg.format, commit.gpgSign, tag.gpgSign, and user.signingKey. Setup fails rather than silently producing unsigned commits when a required signing identity is unavailable.

What happens to the box when I exit?

Nothing — it keeps running in the background so the next xcbox is instant. Use xcbox stop to stop it, xcbox stop --gateway to also stop the shared gateway, or xcbox rm to delete the box (your agent login in ~/.xcbox-home survives either way).

Why do colors or text selection differ in Claude's fullscreen TUI?

xcbox passes the host's TERM, COLORTERM, and terminal identity into the interactive box shell, so a new session uses all of the colors advertised by Terminal.app or another host terminal. Exit and re-enter xcbox after updating to pick up that environment.

Why does it mount my whole repo instead of just the Xcode project folder?

So .git comes along and the agent can commit and push — even when the .xcodeproj lives in a subdirectory of the repo. You still start in your project directory inside the box.

Tests

Standalone bash scripts, run directly:

bin/test-ci.sh            # syntax + ShellCheck + all Linux-safe tests (also runs in GitHub Actions)
bin/test-guard.sh
bin/test-lib.sh
bin/test-project-identity.sh
bin/test-box-home.sh
bin/test-agents.sh           # selection memory + Claude/Codex install and MCP wiring
bin/test-list.sh
bin/test-cleanup.sh          # reset/prune dry runs, deletion scope, running-box protection
bin/test-logs.sh
bin/test-status-probes.sh
bin/test-dispatch.sh
bin/test-doctor.sh
bin/test-subcommands.sh
bin/test-terminal.sh          # terminal capabilities + sandbox marker on interactive entry
bin/test-runtime.sh          # locked install detection + offline reuse + lock refresh
bin/test-agents-container.sh # real throwaway Apple container: install/wire both agents
bin/test-git-signing.sh      # SSH signing config + safety/diagnostic unit coverage
bin/test-git-signing-container.sh # real signed commit through a forwarded throwaway agent
bin/test-gateway.sh          # starts the gateway; verifies a real MCP session
bin/test-gateway-lifecycle.sh # isolated start → stop → restart lifecycle regression
bin/test-loop.sh             # full end-to-end: generate a throwaway app → build + test through the sandbox

GitHub Actions runs bin/test-ci.sh on every push and pull request using a read-only token. The Apple container/Xcode gateway and full iOS build/test loop remain local macOS checks.

Updating the gateway runtime

Gateway dependencies move only through an explicit lockfile update—never through @latest at startup:

npm install --save-exact @modelcontextprotocol/sdk@VERSION xcodebuildmcp@VERSION
bin/test-runtime.sh
bin/test-gateway-lifecycle.sh
bin/test-gateway.sh

Review and commit package.json and package-lock.json together. The next xcbox up detects the new lockfile hash and runs npm ci; unchanged installations continue using their existing local binaries.

Releasing

xcbox releases use numeric Git tags that match VERSION. To publish a release:

  1. Update VERSION, run bin/test-ci.sh, commit, tag, and push the new version.
  2. Update Formula/xcbox.rb in Bunn/homebrew-tap with the new tag URL and source archive SHA-256.
  3. Run brew audit --strict Bunn/tap/xcbox, brew style Bunn/tap/xcbox, and brew test Bunn/tap/xcbox, then push the tap update.

Security model

Trusted-agent. The sandbox isolates the agent's filesystem to your repository — it guards against mistakes and blast radius, not a malicious agent. Builds still run your project's build scripts on the host via xcodebuild, and the container keeps network access (it must reach the gateway). The unauthenticated gateway binds only to host loopback and boxes reach it through Apple container's localhost DNS bridge. Other local processes and containers using that bridge can still reach it, so xcbox remains a trusted-agent tool rather than a boundary against hostile code.

About

No description, website, or topics provided.

Resources

Stars

14 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages