Untrusted Repositories
Opening a directory is not a neutral act. A repository you cloned, or a tarball you extracted, supplies files that other programs read as instructions — .git/config tells git what to run, .envrc tells direnv what to export, AGENTS.md tells the model what to do. None of that requires you to approve anything. It happens because you cd'd somewhere.
So Tapioca treats the working directory as a second untrusted input, alongside the model. This page is what that means in practice: what a repository can put in front of you, what is done about it, and what is still yours to judge.
What the directory controls
Everything in it, which is more than it sounds:
.git/configand.gitattributes— both name programs git will run.envrc— and therefore, if you use direnv, the environment of the process, including$HOMEand$XDG_CONFIG_HOMEAGENTS.md,.tapioca/commands/*.md, skills — text that reaches the model- file names, symlinks, hard links, FIFOs, and every byte of content
Git
Merely opening a directory used to be enough to run code from it. core.fsmonitor is a config key naming a program, git status executes it, and the git panel polls status every few seconds — in every permission mode, before you have typed anything. An extracted tarball is enough; git clone is not, since clone writes a fresh config.
Every git command Tapioca runs now has the keys that name a program pinned away. The list is not fixed, because the dangerous keys are not a fixed set: a filter driver's name is chosen by the repository and selected from its own .gitattributes. So the repository's own configuration is read first — git config --list executes nothing — and anything naming a program is neutralised, across every scope, following include.path.
The pins travel as GIT_CONFIG_KEY_<n>/VALUE_<n> rather than -c key=value, because a config subsection name may legally contain = and -c splits on the first one. GIT_CONFIG_PARAMETERS is dropped from the environment entirely, since git reads it after the numbered pairs — a channel that cannot be ranked below a pin does not travel.
Instruction files
AGENTS.md and anything it @imports reach the system prompt on every turn, in every mode, with no tool call to decline. So they are confined:
- an import resolves inside the project or your config directory, and nowhere else — checked on the resolved path, because git stores symlinks and a committed
AGENTS.mdpointing at~/.config/tapioca/config.tomlwould otherwise put your API keys in the prompt - a file with a second name of its own (a hard link to that config) is refused the same way
- only
.md,.markdownand.txtare importable - one file is capped, the expansion is capped in aggregate, and the total is capped — a
d -> .symlink makes one file reachable under unboundedly many paths - a FIFO named
AGENTS.mdis not read, because opening one blocks until a writer appears and nothing ever writes
A config file inside the tree
Tapioca's config lives outside the working tree. A repository can still reach it by choosing where it is looked up: an .envrc pointing XDG_CONFIG_HOME into the checkout, or a committed file that --settings is aimed at.
When the config Tapioca loaded turns out to live inside the tree being worked on, every key that executes or sends a credential is withdrawn, and you are told which:
| Key | Why |
|---|---|
[[mcp]], [[lsp]], [[agents.external]] | each names a command started at launch |
bash_allow, permissions.allow | standing approvals |
permission_mode = "auto" / "bypass" | forced down to manual |
editor | split into an argv and run on a keystroke |
providers.*.base_url | decides where your API key goes — kept only if it names this machine |
providers.*.credentials_file | names a file whose token_uri is POSTed to |
[[hooks]] | commands run around tool calls |
ask and deny rules are kept. A repository can only narrow with those, and a narrowing it chose is not one you need protecting from.
Your own config at ~/.config/tapioca/config.toml is exempt no matter where you are working, including inside a dotfiles repository — getting a file read as the config is exactly what redirecting XDG_CONFIG_HOME achieves, so one already sitting at the home location was not planted. That check asks the account database as well as $HOME, because $HOME is a variable an .envrc can set.
The environment
Since .envrc reaches the environment, a variable naming a location cannot be allowed to move a defence:
- the protected directories (
~/.ssh,~/.aws, your config and session store) are always on the list; a variable can add a root, never replace one RIPGREP_CONFIG_PATHis dropped fromgrep's child, because ripgrep reads arguments from the file it names and one of them is--pre=COMMAND, run on every file searched — andgrepprompts for nothing, in any mode, plan included- the sandbox hides both the
$HOMEyou have and the one the account database reports
Grants
[p] grants a command word. It does not grant a flag that turns that command into a way of running another program: git -c, git bisect run, git config, find -exec, go run, go generate, go env -w, make -f, tar --use-compress-program, rsync -e and npm exec all re-prompt, and the check runs when the grant is matched, not only when it is offered — the whole point of a blanket grant is that later commands are not read by anyone.
Be clear about what this buys, though: a grant on git is still weaker than that list makes it look. git commit runs .git/hooks/pre-commit, which in an extracted tarball is a file the archive chose. What the list buys is that the command you pressed [p] on cannot be turned into an arbitrary one; it does not make git safe in a hostile checkout.
What this does not cover
PATHandLD_PRELOAD. If you let an.envrcset those,git,rgandshare already whatever it says they are. Nothing Tapioca does inside the process changes that; it is a property of running a program in a shell someone else configured.bypassmode, which disables all of the above.- Build tools doing their job.
make,npm install,cargo buildandgo testrun code from the repository by design. That is what they are for, and it is why the checkpoint exists. - A deny rule cannot see inside
sh -c '…'or$(…). Matching is textual;echo $(touch x)runs underbypassdespite a rule fortouch.
The short version
Use plan or manual in a repository you have not read. direnv allow on a checkout you do not trust is the single largest thing you can hand it — larger than anything the agent will ask you to approve — because it is the one step that lets the directory change the environment the whole process runs in.