Kotoba

Where to start

What Kotoba isWho she is and where she runs, in one page
InstallingOne package, two commands
First runA key, a model, her first words
The approval gateHow she asks before she acts
The two voice modesLocal voice, or the agent tunnel
The soul fileChange who she is
RoadmapWhat grows next, and what was cut on purpose

Or a section

↑↓ move openesc close124 pages
All pages

Security4 of 10

The sandbox setting

About 5 minutes to read

On this page

KOTOBA_SANDBOX takes three values. It decides where — and whether — her shell and execute_code tools run anything.

KOTOBA_SANDBOX=local     # default
KOTOBA_SANDBOX=docker
KOTOBA_SANDBOX=none

It is also live-settable in Settings → Security, and a value saved there is written to ~/.kotoba/settings.yaml, which wins over the environment variable. If api/.env says docker and the settings file says local, the effective value is local. kotoba doctor reports the value actually in force.

A value that is not one of the three is rejected on read as well as on write, with a warning in the log, and the effective value falls back to local. Measured: rocket and an empty string both resolve to local; NONE resolves to none, so case does not matter. Guarding only the settings file reproduced the bug this exists for — the panel rendered local while the backend picker got the invalid value, so execution was silently off with every surface reporting a working sandbox.

local — the default

The name is the one thing about this setting that can mislead, so it is worth being blunt: local is not isolation. The module's own description is accurate — run on the HOST: a child process in a jailed workdir, scrubbed env, timeouts.

What you actually get:

  • A child process on your machine, as your user account. Measured: a command run through this backend printed the machine's hostname, listed /etc, and reported the account name as the account running the server.
  • A starting directory, not a cage. The process starts in the working folder. It is not confined to it. What confines things is the approval gate deciding whether the command runs at all, plus the path jail on her file tools — which is a separate mechanism and does not apply to a shell command once it is running.
  • A scrubbed environment. The child gets an allow-list of names and nothing else. Measured on a process whose parent held OPENAI_API_KEY, ELEVENLABS_API_KEY and GITHUB_PERSONAL_ACCESS_TOKEN: none of the three reached the child. The full list is in Secrets.
  • A named interpreter. POSIX gets sh -c. Windows gets PowerShell invoked by argv with an encoded command, never whatever COMSPEC happens to name.
  • Its own process group, and a timeout. On expiry the whole group gets SIGTERM, then SIGKILL after 5 seconds; the call returns exit code 124 with timed out after Ns and was killed. Measured.
  • Output caps. 200 000 bytes per stream, read continuously so a runaway can neither flood memory nor block on a full pipe.

Temporary files go to ~/.kotoba/tmp, exposed to the child as TMPDIR, TMP and TEMP — deliberately not the system temp directory and not the Files library.

This is the right default for an assistant on a machine you own, and it is the reason the approval gate matters as much as it does. On local, the card is the boundary.

docker — opt-in isolation

Her commands run in a container started per session and reached with docker exec. The flags, read out of the code:

--network none                      no outbound network from the container
--read-only                         the image filesystem is not writable
--tmpfs /tmp:rw,size=64m,mode=1777  a small writable scratch
--memory 512m                       memory cap
--pids-limit 256                    process cap
--cap-drop ALL                      no Linux capabilities
--security-opt no-new-privileges    no setuid escalation
--user <uid>:<gid>                  runs as the server's own user (POSIX; Docker Desktop maps this itself)
-v <workdir>:/work  -w /work        only the working directory comes from the host

The image is python:3.12-slim unless KOTOBA_SANDBOX_IMAGE says otherwise. The host environment is never forwarded: docker exec uses the image's environment only, so no key of yours is inside.

The approval gate is looser here, on purpose. With the container as the boundary, an ordinary command runs without a card, and so does an ordinary Python snippet. Measured with the docker backend selected: ls, npm install, python x.py, cat /etc/passwd and rm notes.txt all run unasked, while anything matching the dangerous list still stops and asks. execute_code behaves the same way and needs no saved grant here — see execute_code.

Two consequences to hold on to:

  • rm notes.txt running unasked means files in your working directory can be deleted without a card. /work is a bind mount of a real host directory; only that directory is exposed, and it is fully writable.
  • cat /etc/passwd running unasked is reading the container's file, not yours.

If you want to be asked about everything, run local. The looser card behaviour is the trade you make for real isolation.

The container is removed at session teardown (docker rm -f). A command still running inside it may outlive a cancelled turn until that happens.

none — no execution at all

The strictest of the three, not the loosest.

What it covers. No tool of hers starts a child process on your machine:

  • shell and execute_code are not offered to the model at all. Measured: with none selected, both tools' availability checks answer False and no sandbox object is created.
  • search_files skips the ripgrep path and answers from its own directory walk instead — a literal substring search, not a regex, bounded by a wall-clock deadline.

What it does not cover. This setting governs the agent, not the application. Two things are installed and approved by their own separate routes and still launch:

  • MCP servers. A stdio MCP server is a process on your machine, started by the MCP layer, not by the sandbox. Installing one goes through its own approval card.
  • The browser. When she uses the browser, a real Chromium-family browser is launched on a dedicated profile, or connected to via KOTOBA_BROWSER_CDP if you point it at one. That launch does not consult KOTOBA_SANDBOX either.

So none means "she runs no commands", not "nothing runs". If you need the stronger statement, do not install MCP servers and do not use the browser toolset.

Choosing

You wantSet
An assistant on your own machine, asked about everything that is not a readlocal (default)
Commands genuinely isolated, at the cost of being asked less oftendocker
No shell and no code execution from her at allnone

Under docker, ordinary commands are out of scope for a vulnerability report — the container is the boundary there, by design. Under local, anything she does after you approve a card is likewise the product working as documented. See Reporting a vulnerability.