Sandbox

Run any task's terminals and hooks inside a sandbox. Two backends: nono's kernel-level access limits in place on the host, or a launcher of your own.

Choose a backend

Sandboxing is a per-terminal choice. Right-click a task and use Open in → nono sandbox or Open in → Custom sandbox; an entry appears for each backend available to the project. Plain Open in → Terminal runs on the host, unsandboxed. Every terminal and hook opened for that task then runs under the chosen backend. A sandboxed terminal carries an outlined status dot and a backend suffix on its label.

Each backend has its own configuration under Project Settings → Sandbox.

nono

nono runs a task's commands in place on the host worktree under kernel-level access limits (Seatbelt on macOS, Landlock on Linux). It is a kernel access policy on a host process rather than a separate machine, so it starts instantly with no file sync.

nono is experimental. Turn it on under Project Settings → Experimental; the nono sandbox entry then appears in a task's Open in menu.

What it does

Terminals and hooks run on the real worktree, wrapped by nono. Filesystem access is limited to the worktree and the repository's git data: .git is read-only apart from read/write overlays at objects/, refs/, logs/, and worktrees/, so commits land while hooks/ and config stay read-only. Ouijit derives these grants from the task's worktree path at spawn time. Network is allowed by default; the local hook-server port stays reachable so agent status still reports back.

Configuration

Under Project Settings → Sandbox → nono:

  • Additional folders. Extra paths granted read/write beyond the worktree and git data, e.g. a shared cache or a monorepo sibling.
  • Block outbound network. Deny outbound connections. The hook-server port stays open so agent status keeps working.
  • Allowed ports. Extra localhost ports to keep reachable, e.g. a dev server on 3000.

Requirements

Ouijit bundles the nono binary and the agent packs it uses; there is nothing extra to install.

  • macOS. Seatbelt (sandbox-exec).
  • Linux. Landlock, which needs kernel 5.13 or newer.

Custom

Read this first. A plain task terminal carries a host-scoped OUIJIT_API_TOKEN. A launcher you run by hand inside one passes that token into the sandbox, where an agent can rewrite hooks and this very setting, so the next spawn runs its command on the host. Either unset OUIJIT_API_TOKEN in a hand-run launcher (status reporting stops), or configure the launcher here so Ouijit spawns it with a sandbox-scoped token.

The Custom backend runs a task's terminals under a launcher you provide: a reviewed script in your team's control, with its own pinned sandbox binary, committed policy, and preflight checks. Ouijit grants nothing itself; the launcher owns the boundary. It is experimental. Turn it on under Project Settings → Experimental, then set the command under Project Settings → Sandbox → Custom or with ouijit sandbox-command set.

What Ouijit passes

Ouijit runs <launcher> [args] -- <shell> [shell args] with the working directory set to the task worktree and the full terminal environment: OUIJIT_API_URL and a sandbox-scoped OUIJIT_API_TOKEN, OUIJIT_PTY_ID, OUIJIT_WRAPPER_DIR, OUIJIT_CLI_PATH (when the bundled CLI is present), OUIJIT_SHELL_INTEGRATION_DIR, and the shell integration's ZDOTDIR. The launcher's argv is exactly the configured command, split on whitespace with shell-style quoting; nothing is expanded and nothing is added except the -- and the shell.

Everything Ouijit would otherwise have granted is exported as advisory hints. They are computed on the host from the project path and Ouijit's own directories, never from the worktree, and a launcher is free to ignore them.

VariableWhat Ouijit's other backends do with it
OUIJIT_SANDBOX_WORKTREEThe task worktree, read+write.
OUIJIT_SANDBOX_GIT_DIRThe repository's .git, read-only.
OUIJIT_SANDBOX_GIT_WRITABLE_DIRSColon-separated objects, refs, logs, worktrees under it, writable so commits land while hooks/ and config stay read-only.
OUIJIT_SANDBOX_HOOK_PORTThe localhost port that must stay reachable for agent status to report back.
OUIJIT_SANDBOX_CACHE_DIRA per-project package-manager cache outside any worktree, read+write. Shared by every task of the project.
OUIJIT_SANDBOX_WRAPPER_DIROuijit's agent shims and CLI reference, read-only. Its bin/ is first on PATH.
OUIJIT_SANDBOX_CLI_DIRThe bundled ouijit CLI the shim runs, read-only. Set only when the bundled CLI is present.

What the launcher is responsible for

  • Exec the argv after -- with the inherited environment intact: at least PATH, ZDOTDIR, every OUIJIT_* variable, TERM, HOME, SHELL, and the locale. Shell integration, the status dot, and the ouijit and agent shims are all carried by the environment; a launcher that scrubs it starts a bare shell.
  • Own the boundary. Ouijit adds no --allow or --read flags.
  • Keep the hook port reachable if you block the network.
  • Exit non-zero to refuse. Whatever it wrote to stderr appears in the terminal, the exit line prints red, and Ouijit raises a "sandbox failed to start" notice. It never falls back to an unsandboxed shell.

Where the launcher may live

An absolute path or a bare name resolved on PATH. Relative paths are refused, and so is any absolute path inside the worktree or the spawn directory: the sandboxed agent can write there, and a launcher it can edit is one it can turn off. Keep a versioned launcher in the repo if you like, but install it from a reviewed checkout to somewhere the sandbox cannot write, such as ~/.local/bin, and point the setting there. If your PATH contains relative entries, use an absolute path.

A restored terminal whose backend is no longer available (the flag was turned off) reopens as a host shell, as it does for the other backends.