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.
| Variable | What Ouijit's other backends do with it |
|---|---|
OUIJIT_SANDBOX_WORKTREE | The task worktree, read+write. |
OUIJIT_SANDBOX_GIT_DIR | The repository's .git, read-only. |
OUIJIT_SANDBOX_GIT_WRITABLE_DIRS | Colon-separated objects, refs, logs, worktrees under it, writable so commits land while hooks/ and config stay read-only. |
OUIJIT_SANDBOX_HOOK_PORT | The localhost port that must stay reachable for agent status to report back. |
OUIJIT_SANDBOX_CACHE_DIR | A per-project package-manager cache outside any worktree, read+write. Shared by every task of the project. |
OUIJIT_SANDBOX_WRAPPER_DIR | Ouijit's agent shims and CLI reference, read-only. Its bin/ is first on PATH. |
OUIJIT_SANDBOX_CLI_DIR | The 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 leastPATH,ZDOTDIR, everyOUIJIT_*variable,TERM,HOME,SHELL, and the locale. Shell integration, the status dot, and theouijitand agent shims are all carried by the environment; a launcher that scrubs it starts a bare shell. - Own the boundary. Ouijit adds no
--allowor--readflags. - 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.