> ## Documentation Index
> Fetch the complete documentation index at: https://astronomer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Otto permissions

<Info>
  **Labs**

  This feature is in [Labs](/docs/astro/feature-previews).
</Info>

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.

| Mode                | Behavior                                                                                                                                                         |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `default`           | Normal behavior. Tools are allowed, denied, or prompted based on your rules.                                                                                     |
| `acceptEdits`       | Auto-allows `edit` and `write` inside the project. Other tools fall through to the normal rules. Safety checks still fire.                                       |
| `confirmEdits`      | Prompts 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. |
| `plan`              | Blocks `edit` and `write` entirely. Restricts `bash` to a read-only allowlist (`ls`, `cat`, `git`, `rg`, `af`, `astro`, etc.).                                   |
| `bypassPermissions` | Allows 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):

| Scope   | Path                                 | Purpose                                         |
| ------- | ------------------------------------ | ----------------------------------------------- |
| User    | `~/.astro/otto/permissions.json`     | Your personal rules across all projects         |
| Project | `.astro/otto/permissions.json`       | Team-wide rules committed to Git                |
| Local   | `.astro/otto/permissions.local.json` | Your 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

```json expandable wrap theme={null}
{
  "enabled": true,
  "defaultMode": "default",
  "restrictToProjectDir": true,
  "rules": {
    "allow": ["Bash(ls:*)"],
    "deny": ["Bash(rm -rf /:*)"],
    "ask": ["Bash(git push:*)"]
  },
  "pathDeny": [
    {
      "id": "no-root-writes",
      "patterns": ["/etc/**", "/usr/**"],
      "tools": ["write", "edit", "bash"],
      "message": "Refusing to write outside user-space."
    }
  ],
  "commandPatterns": [
    {
      "id": "my-dangerous",
      "pattern": "\\bdrop\\s+database\\b",
      "regex": true,
      "behavior": "ask",
      "description": "drop database"
    }
  ],
  "contentPatterns": [
    {
      "id": "no-eval-in-dags",
      "pattern": "eval\\(",
      "regex": true,
      "onTool": ["write", "edit"],
      "pathGlobs": ["**/dags/**/*.py"],
      "behavior": "deny",
      "message": "Avoid eval() inside Dag code."
    }
  ]
}
```

### Rule strings

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

| Rule                   | Effect                                                  |
| ---------------------- | ------------------------------------------------------- |
| `Bash`                 | Matches 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:

```json wrap theme={null}
{
  "commandPatterns": [
    { "id": "astro-deploy", "enabled": false }
  ]
}
```

## 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

```sh wrap theme={null}
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.
