Security1 of 10
The security model
On this page
Kotoba runs on your machine. It holds your API keys, reads and writes files, and — on the default setting — runs shell commands and Python as your own user account.
This section says exactly what stops what, and, with the same care, what does not. Every claim on these pages was checked against the code, and where the code protects less than the wording elsewhere suggests, the page says so.
The four things that decide what she can do
| Control | Where it is set | What it decides |
|---|---|---|
KOTOBA_SANDBOX | api/.env, or Settings → Security | Whether commands run on the host, in a container, or not at all |
| The approval gate | Nothing to configure; grants are saved as you answer cards | Which commands run without asking you |
| The working directory | KOTOBA_WORKSPACE_DIR, default ~/.kotoba/files | Where her file tools may read and write, and where a command starts |
KOTOBA_WEB_PASSWORD | api/.env and .env.local | Whether anything on the network can reach the API at all |
A fifth is worth naming because its absence is the default: with no KOTOBA_WEB_PASSWORD set, the
API is open to anything that can reach the port.
What actually protects you
A card in front of anything that runs. With KOTOBA_SANDBOX=local — the default — every shell
command that is not a plain read inside the working folder stops and asks. Nothing runs before you
answer. The exact line is drawn in The approval gate, measured rather than
described.
A scrubbed environment. A child process started for her gets an allow-list of variable names and
nothing else. Your provider keys are not among them, so a command or snippet she runs cannot read
them out of its own environment. Measured on a Linux desktop: fifteen names reached the child, and
OPENAI_API_KEY, ELEVENLABS_API_KEY and GITHUB_PERSONAL_ACCESS_TOKEN — all present in the
parent — did not. The list is enumerated in Secrets.
A path jail on the file tools. Every path her file tools receive is resolved — symlinks and ..
included — and refused if it leaves the working directory (core/path_security.py).
Encryption at rest for keys you save in the app. AES-256-GCM, with the master key in a separate
file at mode 0600. See Secrets.
A closed API when you close it. One password shuts both the web surface and the model endpoint. See The web gate.
A guard on every URL she can be steered to fetch, and a screen over third-party tool text before it reaches the model. See Fetching and third-party content.
What does not protect you
The working directory is a starting point, not a cage. On KOTOBA_SANDBOX=local an approved
command runs as your own user account, with your own file permissions. It begins in the working
folder, and the auto-approval rules refuse to run a read whose arguments point outside it — but a
command you approve can read anything you can read. Measured: a command run through the local
backend printed /etc/hostname and listed /etc without difficulty. The word "sandbox" in the
setting's name describes where a command starts and what environment it is handed, not a kernel
boundary. Only KOTOBA_SANDBOX=docker is isolation.
Writing a file does not ask. write_file and patch write inside the working directory with no
approval card. They are jailed to that directory and they are recorded in the audit trail, but
nothing pauses for you. Since the working directory is the Files library by default, that includes
overwriting a file already in it.
The dangerous-command list is a denylist over text. It is a set of 14 regular expressions read over the command line. It catches the shapes it names and nothing else. See Dangerous commands.
The Python pre-screen is a denylist over source. Same class of thing, 7 patterns, for
execute_code. It is a speed bump, not a wall. See execute_code.
Reads are not audited. The audit trail records tool calls whose risk is write, exec or
network. Searching the web, fetching a page and reading a file are read and leave no row. What
she read is in the conversation, not in the log.
Settings → Security shows a trust field that does nothing. It is read from KOTOBA_TRUST and
displayed; no code path consults it. api/.env.example labels it "Display-only, not yet enforced".
Treat it as a placeholder, not a control.
The default posture, stated plainly
A fresh install runs commands on your machine as you, asks before anything that is not a plain read, writes files without asking inside one folder, and serves an unauthenticated API on localhost. That is a reasonable posture for a personal assistant on a laptop you own. It is not a reasonable posture for a machine anyone else can reach.
If you want less: KOTOBA_SANDBOX=docker for real isolation, or none to withhold execution
entirely. Set KOTOBA_WEB_PASSWORD before you expose a port. Both are covered in
The sandbox setting and The web gate.
Pages in this section
- The approval gate — what asks, what does not, and the two kinds of grant
- Dangerous commands — the labels, and what a denylist can promise
- The sandbox setting —
local,docker,none - execute_code — the Python path and its pre-screen
- Secrets — the keystore, file permissions, the scrubbed environment
- The web gate — passwords, cookies, CORS, the model endpoint
- What leaves your machine
- Fetching and third-party content — the URL guard and the MCP screen
- Reporting a vulnerability
