Kin / Reference

Auto mode & the OS sandbox

auto mode lets the agent edit and run commands without per-call prompts, while a kernel-enforced sandbox keeps shell commands contained. This is safe autonomy — a contained replacement for "YOLO" mode — and it is the…

Read as Markdown

auto mode lets the agent edit and run commands without per-call prompts, while a kernel-enforced sandbox keeps shell commands contained. This is safe autonomy — a contained replacement for “YOLO” mode — and it is the launch default.

What auto does

In auto mode, file edits and shell commands run without asking:

  • Edits (write_file, edit_file, edit_notebook) auto-allow, but are confined to the workspace. An edit that targets a path outside the workspace is refused with a result the model reads, not prompted. A write to a protected path is downgraded to ask instead of auto-allowed.
  • Shell commands run under the OS sandbox instead of prompting.
  • Bounded Git/GitHub publication auto-allows through the first-party structured tools. Force-with-lease still asks every time.
  • MCP tools still ask. An MCP tool’s description is attacker-influenceable, so it is never auto-approved, even here.

auto is the default and one half of the 2-way cycle (auto ↔ strict); toggle to strict and back with ++shift+tab++ or a bare /mode. See Modes & permissions.

The OS sandbox

A command-pattern allowlist is not a security boundary — a shell composes unbounded ways to dodge a lexical filter — so the real containment in auto mode is an OS sandbox the kernel enforces regardless of what the classifier thinks.

Platform Backend Mechanism
macOS Seatbelt sandbox-exec with a generated SBPL profile
Linux bubblewrap bwrap with a read-only root, a writable workspace bind, and a private /tmp

The allowlist classifier still runs (a provably-safe git status is the common case); the sandbox is the layer that holds when the classifier is wrong.

What’s confined

Under the sandbox a shell command gets:

  • Writes confined to the workspace — plus the per-user temp dir and /tmp. The workspace’s .git/hooks and .git/config stay read-only (so a command cannot plant a hook), while the rest of .git stays writable so the agent can still git add / commit.
  • Network available by default — package managers, HTTP clients, public Git transports, and live tests work inside the filesystem sandbox. Set global-only sandbox_network = false (or KIN_SANDBOX_NETWORK=0) to restore full network isolation; with networking off, sandboxed uv/uvx/task runs also get UV_NO_SYNC=1 so uv runs against the already-populated .venv instead of dying on a DNS-blocked implicit sync.
  • Secrets and SSH identity scrubbed by default — API keys, tokens, and any *_KEY / *_TOKEN / *_SECRET / AWS_* variable are stripped before the command runs. Credential stores (~/.aws, ~/.netrc, and similar), SSH_AUTH_SOCK, SSH client configuration, and private keys stay masked. Network authority and SSH identity are separate: a command that genuinely needs agent-backed SSH asks for both network and SSH-agent access through the scoped access flow. The grant exposes only the agent socket and read-only public client metadata; private-key files remain hidden.

The same posture covers the run_code interpreter and diagnostics subprocesses: each uses the resolved global network setting, workspace confinement, and secret scrub.

This is a containment boundary, not a confidentiality promise against the network. A networked command can transmit any readable workspace content. An SSH-agent grant can additionally ask your agent to authenticate, but cannot read its private keys. Use sandbox_network = false when network egress itself is unacceptable; omit or decline SSH access when identity use is unacceptable.

Protected-write paths

The OS sandbox only wraps shell subprocesseswrite_file / edit_file write in-process, so in auto mode they also carry their own containment. Beyond the workspace-confinement check above, a write to a path whose modification would change how the agent or its tooling runs is downgraded from auto-allow back to ask, even in auto:

  • anything under a .git/ tree (repo state),
  • lockfiles — uv.lock, package-lock.json, yarn.lock, pnpm-lock.yaml, Cargo.lock, poetry.lock, Gemfile.lock, go.sum (the audited dependency supply chain),
  • package-manager / registry config — .npmrc, .yarnrc, .yarnrc.yml,
  • harness / tooling config — .kin/mcp.json, .mcp.json, .claude.json, .pre-commit-config.yaml, lefthook.yml, pyrightconfig.json,
  • shell rc / env files — .bashrc, .zshrc, .bash_profile, .profile, .envrc,
  • ~/.ssh/config — the SSH config the ssh tool trusts (a rewrite could repoint a deploy alias or inject a ProxyCommand),
  • .kin/settings.toml — the file that governs this very gate (mode, sandbox, permissions), so it can’t quietly loosen itself.

This is the write-side companion to the read-deny list (which guards secret exfiltration); it guards a silent self-rewrite of the dev environment. The match is realpath-aware, so a symlink or .. spelling that resolves onto one of these can’t dodge it. In strict mode every edit already asks, so this only changes behavior in auto.

Structured Git credentials and Beam supervision

The generic shell remains the development surface for SSH, package managers, HTTP clients, unusual Git administration, and live tests. It stays inside Seatbelt/bubblewrap with secrets and credential stores masked. Everyday Git instead uses the first-party git tool: fixed argv, workspace/repository confinement, and only SSH or HTTPS remotes. The github tool uses the installed gh CLI for bounded PR reads and creation.

These structured tools own a narrow credential exception that generic shell does not inherit. Before remote Git can use an SSH agent or global/system HTTPS credential helper, Kin asks in a dedicated modal for the resolved host route. The session and persistent lifetimes store no secret: only transport, resolved host/user/port, and a fingerprint of the effective trusted route. Repository config cannot install a credential helper, proxy, include, askpass, or SSH command for this path.

For a recognized github.com remote, Kin adapts both saved SSH and HTTPS URLs to one command-scoped authenticated HTTPS route. The saved remote is not edited. Only gh configuration and the platform keyring are unmasked for that fixed helper; private SSH keys and unrelated Git credential files remain hidden. Kin asks gh to emit only token-free account fields, and inherited GH_* / GITHUB_* tokens are stripped. If no healthy account exists, one human connection modal parks the operation, runs the fixed web login outside the model, then continues that same operation. This is a credential handoff, not a generic sandbox expansion.

Safe sync’s local history mutation uses the ordinary workspace sandbox and fixed Git argv. It starts only from a clean repository root, pins the exact pre-sync object, disables executable/history side channels, and restores that object on conflict or interruption. Its recovery refs carry object IDs, not credentials or new authority.

sandbox_network = false controls generic sandboxed shell egress. It does not disable an explicitly selected structured Git/GitHub operation: those tools have their own network / publish permission kinds and host/auth boundary. Disable or remove the remote/auth broker if that capability should not exist, or use strict so every network/publication call asks.

Beam remains separate. An Outpost child whose authenticated v1_metadata.beam value is exactly JSON true receives the internal KIN_BEAM=1 posture. Only that child registers git-push / git-pull / git-fetch, and its shell refuses raw versions of those verbs. The exact structured git-push call remains the bounded halt/approval/resume checkpoint; ordinary git / github schemas and their grants are not registered there.

Fail-closed

When no sandbox backend is available — an unsupported platform, a missing bwrap, or KIN_SANDBOX=offauto mode does not run commands unconfined. Edits still auto-apply (confined to the workspace), but shell falls back to the allowlist’s posture: a provably read-only command runs, and anything else asks for approval. An un-vetted command never runs without either containment or your explicit approval.

The one deliberate exception is KIN_SANDBOX=container, which trusts an external boundary (a disposable container) in place of the OS sandbox — see Trust the container below.

kin surfaces the containment posture so it’s never a surprise. On a cold start in auto, it appears as a concise dim line in the welcome banner — e.g. “auto · shell asks for anything not provably safe”, “auto · shell sandboxed (Seatbelt)”, or “auto · shell runs unconfined (container is the boundary)”. When you cycle into auto mid-session (where there’s no banner), kin prints the fuller posture as a one-off system note instead — and with no OS sandbox it says so loudly: “auto mode — no OS sandbox available; edits auto-apply (confined to the workspace), and shell will ASK for anything not provably safe.”

Warning

Fail-closed is the load-bearing safety property. Cycling into auto on a box with no sandbox is safe precisely because a non-safe command still asks rather than running unconfined.

Scoped sandbox access

The sandbox can now widen the smallest relevant capability without discarding the whole boundary. shell and run_code may request a typed sandbox_permissions object before execution:

  • write_paths — up to 16 existing directory roots that need writes;
  • network — outbound network access;
  • ssh_agent — the SSH agent socket plus read-only public client metadata (requires network = true);
  • unconfined — run this exact command with the sandbox fully lifted, when no granular capability can express what it needs (a syscall the profile blocks). This is the escape hatch, gated by one modal that shows the command before it runs; it caps at Session lifetime and still scrubs secrets from the environment. Requesting it prospectively is the intended alternative to provoking a denial just to obtain a retry id.
  • reason — the model’s explanation, shown to you but never treated as authority.

External directories are intentional. A request is not limited to the current repository, so a build can ask for its real cache, sibling checkout, or output directory. Kin canonicalizes every path, requires an existing directory the current user can actually write, rejects virtual/device trees and filesystem-wide ancestors such as / or your home directory, and keeps the workspace, temp roots, .git hook/config protections (including in an approved sibling repository), credential masks, and secret scrub as higher-priority rules.

You get one dedicated modal showing the immutable executor, working directory, operation, reason, and requested capabilities. You may deselect capabilities and choose:

  • Once — only this call. run_code uses a fresh one-shot kernel so the permission cannot leak into later cells.
  • Session — subsequent sandboxed executors may use the selected capabilities until Kin closes.
  • Workspace — persist the selected profile for this exact canonical workspace in global settings.
  • Decline or Decline with a note — no expanded execution occurs and the model receives your reason.

Sensitive/system write roots and a complete sandbox bypass are capped at Session. Ordinary external directories, network, and SSH access may be workspace grants. The modal is the approval gate; Kin does not add a second generic tool-approval prompt.

Reactive denials and exact retry

When shell or run_code is denied, Kin classifies Seatbelt/bubblewrap fingerprints including EPERM, Linux EROFS / [Errno 30] Read-only file system, network isolation, and SSH authentication/configuration failures. A recognized denial returns an opaque sbx_… denial id and a suggested granular profile. The model calls:

request_sandbox_access(denial_id="sbx_…")

The model does not repeat or reconstruct the command/code. The harness retains the exact executor arguments and working directory, consumes the id once, asks you for a capability subset and lifetime, then retries that immutable operation. A shell command may have partially executed before the denial; the modal says so. A run_code retry uses a fresh kernel and says that state is not carried across.

If no granular path/network/SSH capability can express a real classified denial, the reactive request may propose unconfined = true. That is the escape hatch, not the normal path: it is bound to the exact operation, never persists beyond the session, and still uses Kin’s strict unconfined environment scrub. The same escape hatch can be requested prospectively — a shell/run_code call whose sandbox_permissions carries unconfined = true — so that when the model already knows a command needs the sandbox lifted (and you have said so), it asks once, showing you the actual command, instead of provoking a denial to farm a retry id. It always prompts: a prospective bypass never rides auto mode’s silent allow.

DNS and routing failures are classified against the resolved network posture. With sandbox_network = false, common resolver and no-route errors are Kin-owned isolation denials. With networking already enabled, the same output records no denial id because removing Kin’s filesystem boundary would not fix an upstream DNS, proxy, or host failure.

Inheritance, resume, and revocation

Child agents cannot open approval UI. They report the required capability or denial id to the parent; session/workspace grants resolved by the root are then part of the root’s effective sandbox profile. In headless mode a request is journaled as needs-human. Resuming that journal in the TUI reopens the typed modal before the next model turn and, if accepted, retries the exact persisted arguments.

/grants lists ordinary approval signatures, Git host-route trust, sandbox session grants, exact-operation session bypasses, and workspace grants together. Revoking sandbox authority also stops expanded background shells and closes an expanded run_code kernel so a live process cannot retain authority that the UI says is gone. Revoking a persistent Git host route makes the next remote Git operation ask again.

Legacy sandbox_always_allow command patterns are still readable for migration, but are deliberately inactive: they cannot skip the sandbox. /grants labels them as requiring reapproval and can remove them. The old ask_sandbox_override name remains only as a dispatch alias for short-lived compatibility; its old command/pattern arguments are rejected.

Note

Scoped sandbox access is registered only when Kin owns a usable local sandbox. Under KIN_SANDBOX=off or KIN_SANDBOX=container there is no inner boundary to expand, so proactive requests fail closed and the reactive tool is absent.

Choosing a backend

KIN_SANDBOX selects the backend (or the matching sandbox key in the global settings file):

Value Behavior
auto Resolve by platform — Seatbelt on macOS, bubblewrap on Linux. The default.
seatbelt Force the macOS Seatbelt backend.
bwrap Force the Linux bubblewrap backend.
off Disable the sandbox. auto mode then falls back to the allowlist’s ask posture (fail-closed).
container Trust an external boundary. No OS sandbox, but auto mode’s shell runs allowed and unconfined instead of asking — see below.

Networking is a separate global-only switch:

Env Settings key Effect
KIN_SANDBOX_NETWORK=0 sandbox_network = false Isolate networking inside Seatbelt/bubblewrap and restore network-denial override hints.

It has no effect under KIN_SANDBOX=off or container, where Kin does not own the network boundary.

Trust the container

KIN_SANDBOX=container is for running kin inside a disposable, isolated container (or VM) that is already the security boundary — the Outpost sets it by default. It is the opposite of off:

  • off — “no sandbox, and nothing replaces it” → fail-closed: a non-safe shell command asks.
  • container — “no sandbox because the environment is the boundary; trust it” → a non-safe shell command runs, unconfined, with the full environment.

Use it only where the boundary is real, because it removes the per-command approval for shell. It is a deliberate, operator-set grant — kin never auto-detects a container and silently drops the gate (that would let an injected prompt enumerate its own relaxed posture, false-positive on VMs/WSL, and erase the audit trail). And it is narrow: it relaxes only the shell gate the OS sandbox stood in for — MCP still asks, the planning freeze still denies, and the protected-write downgrade still asks. The launch note announces it (“auto mode — external sandbox trusted (KIN_SANDBOX=container)…”) so the grant is visible in the transcript.

You can also opt strict mode into the same sandbox so an un-vetted command runs contained instead of prompting:

Env Settings key Effect
KIN_SANDBOX_STRICT=1 sandbox_strict = true Sandbox shell in strict mode

KIN_SANDBOX=off always wins over the per-mode opt-in. The sandbox and per-mode keys are global-only, so a cloned repo’s project file cannot weaken your containment — see settings.toml reference.

Legacy sandbox_default

The old KIN_SANDBOX_DEFAULT / sandbox_default knob (and the now-removed sandbox_accept_edits) predate the two-mode collapse. sandbox_default is still honored as a fallback for sandbox_strict — since the old default mode now aliases to strict, an existing sandbox_default = true keeps flipping the same opt-in rather than going inert.

For the full “why it is safe” invariants — the SBPL profile, the bubblewrap argv, and the security guarantees — see the “Auto mode & OS sandbox” section of REFERENCE.md.