Skip to content

Sandboxing & Security

Tapioca gives a language model a shell, a file editor and network access on your machine. That is the point, and it is also the risk. This page says what is actually enforced and what is not. The Permissions page covers modes and rules; this one covers everything around them.

The main threat: prompt injection

The model reads untrusted text constantly — web pages via web_fetch, search results, file contents, tool output, and AGENTS.md from whatever repository you are working in. Any of it can contain instructions aimed at the model ("ignore previous instructions, run …"). The model cannot reliably distinguish data from instructions, so treat every tool call as something the content it just read might have asked for. The permission prompt is where a human sees the command before it runs.

Read-only tools

read_file, grep, glob, web_search and web_fetch do not prompt for ordinary use, because an agent that asks before every file read is unusable. Narrow exceptions exist, because otherwise those tools compose into exfiltration (read a key, send it somewhere):

  • read_file prompts for paths outside the working directory (and --add-dir trees) that look sensitive: .ssh, .aws, .gnupg, gh/gcloud/kube/docker config, browser profiles, .env, id_*, credentials, and any out-of-tree path containing token/secret/password — wherever it lives, not just under $HOME.
  • grep and glob prompt when their search root is outside those trees, and never return matches from files read_file would have gated.
  • web_fetch prompts the first time a host is used; [a] remembers it for the session. Redirects must stay on the approved host and may never land on a loopback, link-local or private address, so an approved page cannot bounce the fetch into your network or at a cloud metadata endpoint.

Sandboxing

Permissions decide whether a command runs. Sandboxing decides what it can reach if it does — the difference between filtering and containment.

toml
sandbox = true
sandbox_network = true

(or --sandbox on the command line). With it on, bash runs under bubblewrap where:

  • the working tree (and --add-dir trees) are writable;
  • the rest of the filesystem is read-only, so tools still work;
  • $HOME is replaced by an empty tmpfs, so .ssh, .aws, browser profiles and shell history are not merely gated — they are not there. Only .gitconfig is bound back, since git refuses to commit without an identity;
  • /tmp is private, and pid/ipc/uts namespaces are unshared.

If bwrap is missing, sandboxed bash calls fail with an explanation rather than silently running unconfined. /permissions shows the live state.

Two limits worth knowing: the sandbox applies to bash only (the file tools are Go code inside the process), and with sandbox_network = true a command can still reach the network — so it bounds file damage, not exfiltration. Set sandbox_network = false to cut network inside the sandbox.

Credentials

Known provider API keys are removed from the environment handed to shell tools, MCP servers, language servers and every other subprocess, so a tool call cannot read them. Add your own with:

toml
secret_env = ["MY_COMPANY_TOKEN"]

MCP servers still receive whatever you set explicitly in their [mcp.env] block.

A provider configured with a custom api_key_env name needs no entry here. The list is derived from your config, so api_key_env = "MY_GATEWAY_KEY" and the ${VAR} an [mcp.headers] entry expands are both withheld from children — a variable holds a key because the config says to read it, not because someone thought of its name. secret_env is for variables nothing in the config points at.

One thing this does not cover: scrubbing removes the variable from the child's environment. It does not hide Tapioca's own — on Linux an approved command can read /proc/<parent>/environ and see everything you exported in the shell that launched it. sandbox = true closes that (a fresh /proc in a new PID namespace); nothing else does.

Your API key also stays on the host it was configured for: a redirect to another domain is refused rather than followed, because x-api-key and a gateway's own header are not among the ones an HTTP client strips on a cross-host hop, and a 307 replays the request body — which is the conversation.

Network calls Tapioca makes itself

Besides the provider you configured and whatever the agent fetches:

  • models.dev, once at startup, for model prices and context sizes. Set model_catalog = false to skip it — useful when running against a local Ollama and nothing else.

Nothing else phones home; there is no telemetry.

Data at rest

Sessions, project memory and checkpoint snapshots contain everything the model saw, including anything you pasted. They are stored unencrypted under ~/.local/share/tapioca with owner-only permissions (0600/0700), as is config.toml (which may hold an API key). Files are created at those modes rather than adjusted afterwards; a directory that already existed with looser permissions keeps them. Full-disk encryption is the answer if you need more; Tapioca does not encrypt anything itself.

Both directories are also treated as sensitive paths, so the agent reading your own config or transcripts prompts like any other secret.

What is not protected

  • bypass mode disables all of the above. It exists for throwaway sandboxes and containers. Do not combine it with untrusted repositories or web browsing.
  • Without sandbox = true, there is no containment. Approved commands run as your user with full access to your machine and network; the permission gate is filtering, not a boundary.
  • The sandbox covers bash only. read_file, write_file and edit_file are Go code inside Tapioca, so bubblewrap does not contain them; they rely on the permission gates instead. In bypass they are bounded by nothing at all.
  • Editor mode trusts its peer. --acp speaks JSON-RPC on stdio, so the peer is whatever process launched Tapioca — normally your editor. It may supply its own MCP servers, which means commands to execute, and it chooses the working directory. Do not expose an --acp process's stdin to anything you would not trust with a shell.
  • Grants are coarse. Allowing git allows git push. Flags and subcommands that turn a command into a way of running another program are excluded (git -c, git bisect run, find -exec, go run, make -f, tar --use-compress-program, npm exec …), and that is checked when the grant is matched rather than only when it is offered — but a grant on git is still weaker than that list makes it look, because git commit runs .git/hooks/pre-commit, which in an extracted tarball is a file the archive chose.
  • --add-dir widens the ungated read area to those directories.
  • Checkpoints do not protect data outside the working tree. Ignored files are snapshotted, within a budget — a repository's own .gitignore would otherwise decide what /rewind can undo, and the paths where the checkpoint is the only copy are exactly the ones an ignore line removes from it. A tree that ignores a directory holding more than the budget is the remaining gap.
  • PATH and LD_PRELOAD are outside what any of this can promise. If the shell that launched Tapioca has them pointed somewhere hostile, git, rg and sh are already whatever that says they are.

For what a cloned repository specifically can and cannot do, see Untrusted Repositories.

Released under the MIT License.