Otto permissions

Labs
This feature is in Labs.

Otto ships a first-party permission layer that gates every tool call. Each call resolves to allow, ask (prompt the user), or deny based on the active permission mode and your configured rules.

Out of the box, Otto asks before running destructive Astro and Airflow commands, prompts before writing to sensitive files like .env or .ssh/*, and restricts writes outside your project folder. You can extend or relax these behaviors through config files and permission modes.

Permission modes

Otto runs in one of five modes. Cycle through modes interactively with Ctrl+] or the /permissions command.

ModeBehavior
defaultNormal behavior. Tools are allowed, denied, or prompted based on your rules.
acceptEditsAuto-allows edit and write inside the project. Other tools fall through to the normal rules. Safety checks still fire.
confirmEditsPrompts before edit, write, and non-read-only bash. Allow rules can’t bypass the prompt. Read tools and read-only bash fall through to the normal rules.
planBlocks edit and write entirely. Restricts bash to a read-only allowlist (ls, cat, git, rg, af, astro, etc.).
bypassPermissionsAllows everything except bypass-immune safety checks on sensitive files and out-of-project writes.

Set the starting mode with the --permission-mode <mode> flag or the OTTO_PERMISSION_MODE environment variable.

Where configuration lives

Otto reads permissions from three files, merged in order (later wins):

ScopePathPurpose
User~/.astro/otto/permissions.jsonYour personal rules across all projects
Project.astro/otto/permissions.jsonTeam-wide rules committed to Git
Local.astro/otto/permissions.local.jsonYour local-only overrides (gitignore this file)

Commit project-scope rules to your repository so every engineer on the team gets the same posture.

Configuration file shape

1{
2 "enabled": true,
3 "defaultMode": "default",
4 "restrictToProjectDir": true,
5 "rules": {
6 "allow": ["Bash(ls:*)"],
7 "deny": ["Bash(rm -rf /:*)"],
8 "ask": ["Bash(git push:*)"]
9 },
10 "pathDeny": [
11 {
12 "id": "no-root-writes",
13 "patterns": ["/etc/**", "/usr/**"],
14 "tools": ["write", "edit", "bash"],
15 "message": "Refusing to write outside user-space."
16 }
17 ],
18 "commandPatterns": [
19 {
20 "id": "my-dangerous",
21 "pattern": "\\bdrop\\s+database\\b",
22 "regex": true,
23 "behavior": "ask",
24 "description": "drop database"
25 }
26 ],
27 "contentPatterns": [
28 {
29 "id": "no-eval-in-dags",
30 "pattern": "eval\\(",
31 "regex": true,
32 "onTool": ["write", "edit"],
33 "pathGlobs": ["**/dags/**/*.py"],
34 "behavior": "deny",
35 "message": "Avoid eval() inside Dag code."
36 }
37 ]
38}

Rule strings

Rules use the format Tool or Tool(content). Content can be a command prefix, a path glob, or * to match everything.

RuleEffect
BashMatches every bash call
Bash(astro deploy:*)Matches bash commands starting with astro deploy
Write(/etc/**)Matches write calls whose path glob-matches /etc/**
Read(~/.ssh/**)Matches read calls with ~ expansion

Path deny

pathDeny blocks writes and edits against a glob list, including bash redirects. Use it to protect folders regardless of the command used to reach them.

Command patterns

Bash-only patterns that match the full command string. Use regex: true for regular expressions. commandPatterns with behavior: "ask" fire even in bypassPermissions mode.

Content patterns

Scan the body of write and edit calls against a pattern. behavior: "deny" blocks the write before it hits disk. behavior: "warn" appends an advisory without blocking.

Built-in safety checks

Otto ships compiled-in protections that fire regardless of configuration:

  • Sensitive files — prompts before any tool touches .env*, ~/.ssh/**, ~/.aws/**, shell rc files, or similar common secret locations.
  • Destructive Astro and Airflow commands — prompts before astro deploy, astro deployment delete, astro workspace delete, astro organization delete, astro dev kill, af dags delete, af runs delete, af tasks clear, af connections delete, af variables delete, and similar destructive commands.
  • Out-of-project writes — prompts before write, edit, or a bash command targets a path outside your project folder.

These checks are bypass-immune — they fire even when the mode is bypassPermissions or --skip-permissions is set.

Disable an individual destructive-command entry in your config by id:

1{
2 "commandPatterns": [
3 { "id": "astro-deploy", "enabled": false }
4 ]
5}

Inspect the current state

Run /permissions in an Otto session to see the current mode, rule counts, and the file paths for each config scope.

Disable permissions for a session

$astro otto --skip-permissions

This coerces the mode to bypassPermissions and prevents Ctrl+] from cycling out of it for the session. Bypass-immune safety checks still fire. Use it only for scripted flows you’re reviewing by hand.