# Permission Rules

> Precisely constrain what CodeBuddy Code can do with fine-grained allow / ask / deny rules, Permission Modes, and layered settings. Rules can be committed to the repository and shared with the team, or overridden locally by developers.

## Permission System Overview

CodeBuddy Code uses a layered evaluation mechanism to balance capability and security. Before each tool call, the following eight phases are evaluated in order:

| Phase | Check | Behavior on match |
| :---- | :---- | :---------------- |
| 1 | **Deny rules** | Reject immediately, with the highest priority |
| 2 | **Trusted Allow rules** (user / cli / flag / session / policy / project under a trusted directory / `--allowedTools`) | Allow immediately, and **can bypass command safety checks** |
| 3 | **Command safety check** (interactive only) | Force confirmation for high-risk / dangerous commands |
| 4 | **Ask rules** | Force confirmation |
| 5 | **Bypass mode short-circuit** | Allow directly in `bypassPermissions` mode; downgrade to default when `disableBypassPermissionsMode` is enabled |
| 6 | **Untrusted Allow rules** (project / local outside trusted directories, plus command / sandbox sources) | Allow, but **cannot bypass command safety checks** |
| 7 | **Permission Mode strategy** | Decide whether approval is required based on the current [Permission Mode](permission-modes.md) |
| 8 | dontAsk / async agent fallback | Convert `ask` to `deny` in print / async agent scenarios to avoid deadlock |

> **Key point**: deny always takes precedence, and allow is split into two layers: "trusted" and "untrusted". Before the project root is listed as a trusted directory, allow rules in `.codebuddy/settings.json` **cannot bypass the command safety gate**. This is designed to prevent malicious repositories from bypassing local safety protections by pushing their own settings into team PRs.

Read-only tools (Read / Grep / Glob, etc.) do not prompt for approval by default inside trusted directories. Editing tools and Bash always go through the full phase chain.

## Three Rule Behaviors

The three arrays under the `permissions` object correspond to three behaviors:

```json
{
  "permissions": {
    "allow": ["Bash(npm test)", "Read(/tmp/data/**)"],
    "ask":   ["WebFetch"],
    "deny":  ["Bash(rm -rf *)", "Edit(.git/**)"]
  }
}
```

- **`allow`**: CodeBuddy can use it without asking for approval
- **`ask`**: Ask for approval every time it is used
- **`deny`**: Never allow it to be used

## Where to Manage Rules

### `/permissions` command

Enter `/permissions` in a session to open the permission management panel. You can view all current allow / ask / deny rules, which settings layer they come from, and temporarily add or remove them (written to the user, project, or project-local scope).

When **"Yes, don't ask again"** is selected in the dialog, CodeBuddy writes the most stable prefix for the current command into the `allow` array of the corresponding scope's settings.

### CLI Startup Arguments

| Argument | Purpose |
| :------- | :------ |
| `--allowedTools <tools...>` | Process-level temporary allow rules. Separated by spaces or commas. Example: `--allowedTools "Bash(git:*) Edit"` |
| `--disallowedTools <tools...>` | Process-level temporary deny rules. Same as above |
| `--add-dir <path>` | Add an extra directory to the trusted directory scope (affects whether Read requires confirmation) |
| `-y` / `--dangerously-skip-permissions` | Equivalent to `--permission-mode bypassPermissions` |

### Configuration Files

See [Settings Configuration](settings.md) for details. CodeBuddy merges these four scopes:

| Scope | Path |
| :---- | :--- |
| user | `~/.codebuddy/settings.json` |
| project | `<repo>/.codebuddy/settings.json` (committed to git) |
| project-local | `<repo>/.codebuddy/settings.local.json` (not committed to git, local override) |
| cliArg / flagSettings / session / policySettings | Process state, not persisted to disk |

## Rule Syntax

Rule form: `Tool` or `Tool(specifier)`.

### Match an Entire Tool

Remove the parentheses to match all calls to a tool:

| Rule | Meaning |
| :--- | :------ |
| `Bash` | All Bash commands |
| `WebFetch` | All web fetches |
| `Read` | All file reads |
| `Edit` | All file edits |

`*` can also be used as a standalone rule, equivalent to a full-match wildcard.

### Add a Specifier for Fine-Grained Matching

Write arguments inside the parentheses:

| Rule | Matches |
| :--- | :------ |
| `Bash(npm run build)` | Exact match for `npm run build` |
| `Bash(npm:*)` or `Bash(npm *)` | All commands starting with `npm` |
| `Read(./.env)` | `.env` in the current directory |
| `Edit(/src/**/*.ts)` | `src/**/*.ts` under the project root |
| `Read(~/.zshrc)` | `.zshrc` in the user's home directory |
| `Read(//tmp/scratch.txt)` | Absolute filesystem path `/tmp/scratch.txt` |
| `WebFetch(domain:example.com)` | Fetch example.com |
| `mcp__puppeteer__navigate` | The navigate tool of the MCP puppeteer service |
| `Agent(Explore)` | The Explore subagent |

## Tool-Specific Rules

### Bash

Bash rules support three syntax forms:

| Syntax | Meaning | Example |
| :----- | :------ | :------ |
| Exact match | The pattern is exactly equal to the command | `Bash(npm run build)` only matches `npm run build` |
| `:*` prefix | The pattern ends with `:*` → matches the first word / multi-word prefix of the command | `Bash(git:*)` matches `git status` / `git push origin main` |
| Wildcard | When the pattern contains `*`, matches as a bash glob pattern (**`*` can cross `/`**) | `Bash(npm run *)` matches `npm run build`; `Bash(ls *)` matches `ls -al /tmp/x` |

> The wildcard mode deliberately lets `*` cross `/`. Otherwise, `ls *` could not match `ls -al /xxx`, which is one of the most common pitfalls for users.

#### Compound Commands

CodeBuddy parses shell operators `&&` / `||` / `;` / `|` and evaluates each subcommand independently:

- **deny / ask rules**: trigger if any subcommand matches
- **allow rules**: require **all subcommands to match** before allowing — a compound command with one matching and one non-matching subcommand will still ask for approval, preventing attackers from hiding dangerous commands next to allowed ones

Example:

```text
allow: ["Bash(git:*)"]

git status         → allow
git status && rm * → ask (rm * is not in allow; every subcommand must match)
git status; rm *   → ask (same as above)
```

#### Redirection

Commands containing `>` / `<` / `>>` / `<<` / `&>` require an **exact match** under allow rules; wildcard rules do not apply.

### Read / Edit / Write

File rules use glob matching and perform three layers of path normalization:

| pattern | Explanation | Example |
| :------ | :---------- | :------ |
| `//path` | Absolute filesystem path | `Read(//etc/hosts)` |
| `~/path` | Starting from the user's home directory | `Read(~/.zshrc)` |
| `/path` | Starting from the project root | `Edit(/src/**/*.ts)` |
| `path` or `./path` | Starting from the current working directory | `Read(.env)` |

Matching options: dotfiles allowed, case-insensitive, bare filenames can match at any depth.

Notes:

- deny rules such as `Edit(.git/**)` block all attempts through Edit / Write / NotebookEdit, but **do not block** indirect paths through Bash such as `python -c 'open(".git/config", "w")...'`. OS-level protection depends on [Bash sandboxing](bash-sandboxing.md)
- Read rules are also intercepted and parsed for some Bash file-read commands (`cat`, `head`, `tail`, etc.)

### WebFetch

```text
WebFetch                       # any URL
WebFetch(domain:example.com)   # only example.com and its subdomains
```

Supports the `domain:` prefix for hostname matching (including subdomains).

### MCP Tools

MCP tools are named in the format `mcp__<server>__<tool>`. Rules support three granularities:

| Rule | Matches |
| :--- | :------ |
| `mcp__puppeteer` | All tools of the entire puppeteer server |
| `mcp__puppeteer__*` | Same as above (wildcard form) |
| `mcp__puppeteer__navigate` | Only the navigate tool |

Tool names starting with `mcp__` are matched as MCP rules.

### Agent (Subagent)

```json
{
  "permissions": {
    "deny": ["Agent(Explore)", "Agent(Plan)"]
  }
}
```

You can also use a CLI flag:

```bash
codebuddy --disallowedTools "Agent(Explore) Agent(Plan)"
```

After being denied, that `subagent_type` will be rejected when the main agent invokes the Agent tool.

### Skill

```json
{
  "permissions": {
    "deny": ["Skill(dangerous-skill-name)"]
  }
}
```

Skill rules **must** be exact matches; wildcards are not supported.

## Trusted Directories

By default, CodeBuddy only considers the **current working directory** trusted. The Read tool is allowed inside trusted directories and asks outside them; Edit / Bash always go through full approval (unless an allow rule applies or a permissive mode is active).

Several ways to expand the trusted scope:

| Method | Persistence |
| :----- | :---------- |
| `--add-dir <path>` startup argument | Process-level |
| `/add-dir` command within a session | Session-level |
| `permissions.additionalDirectories` setting | Persistent |
| `permissions.trustedDirectories` setting | Persistent |

The effective trusted directory list = workspace root + `settings.trustedDirectories` + directories added at startup with `--add-dir` or in-session with `/add-dir`.

> Both `--add-dir` and `permissions.additionalDirectories` only grant **file access**. They **do not** make CodeBuddy load `.codebuddy/` configuration from those directories (agents / hooks / settings, etc. still use the startup directory).

### Project Directory Trust Switch

Whether you have "explicitly trusted" a repository directory also affects the trust level of allow rules. When the directory is **not trusted**:

- `allow` rules in `<repo>/.codebuddy/settings.json` and `.codebuddy/settings.local.json` are classified into the **untrusted** layer (Phase 6), and **cannot** bypass command safety checks
- After the user confirms trust in the interactive UI, project-level rules are promoted to the Phase 2 trusted layer

This is designed to reduce lateral risk that can arise immediately after cloning and running a repository.

## Protected Files / Paths

In any mode, CodeBuddy adds extra protection for a set of critical paths (matching [Permission Modes](permission-modes.md#protected-critical-files)):

- The repository itself: `.git`, `.gitconfig`, `.gitmodules`
- Shell configuration: `.bashrc` / `.zshrc` / `.envrc`, etc.
- Package management: `.npmrc` / `.yarnrc` / `bunfig.toml`, etc.
- IDE tools: `.vscode` / `.idea` / `.husky` / `.devcontainer`
- CodeBuddy itself: `.codebuddy` (except `.codebuddy/worktrees`)
- MCP / configuration: `.mcp.json` / `.codebuddy.json`

`bypassPermissions` mode still allows most of these through, but "catastrophic commands" such as `rm -rf /` / `rm -rf ~` are forced to ask.

## Extending Permissions with Hooks

The `PreToolUse` hook in the [Hooks system](hooks-guide.md) runs before permission approval prompts and can programmatically allow / deny / rewrite inputs.

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "/path/to/bash-policy.sh" }]
      }
    ]
  }
}
```

Hook exit code semantics:

| Exit code | Behavior |
| :-------- | :------- |
| `0` + JSON decision | Execute according to `permissionDecision` (allow / ask / deny) in the JSON |
| `2` | Block (stderr content is passed back to the model) |
| Other non-0 | Non-blocking error (shown but allowed) |

Notes:

- **deny / ask rules always take effect before hooks**. A hook returning `"allow"` cannot bypass deny in settings
- However, **blocking hooks** (exit code 2) can short-circuit allow rules before Phase 1, so you can "allow all Bash first, but use hooks to separately block a few specific commands"

## Working with Sandboxing

Permission rules and [Bash sandboxing](bash-sandboxing.md) are complementary layers:

- **Rule layer**: constrains whether CodeBuddy "wants to use" a tool or access a path
- **Sandbox layer**: constrains whether the Bash subprocess can "actually access" a resource at the OS layer

Typical defense-in-depth combinations:

- `deny` rules prevent CodeBuddy from actively attempting restricted tools
- The sandbox prevents all Bash subprocesses from reaching files / networks outside the allowlist — even if prompt injection makes CodeBuddy want to bypass it, it cannot
- WebFetch `domain:` allow rules and sandbox `allowedDomains` both apply, and the final boundary is the intersection

## Settings Priority

Permission rules inherit the general [Settings priority](settings.md):

```
flagSettings / cliArg / session  > userSettings > policySettings >
projectSettings > localSettings > command/sandbox sources
```

However, the **evaluation order** is more important than settings priority:

- `deny` arrays are merged from all scopes, and **a deny in any scope rejects**
- `allow` arrays are merged twice by "trusted / untrusted": user / cli / flag / session / policy are always included in the trusted merge; project / local are considered trusted only when the directory is trusted
- `disableBypassPermissionsMode` takes effect when it is `"disable"` in any of the four layers: user / project / local / CLI startup flag

## Example Configurations

### Minimized Trust: Only Allow npm Tests + Reading Project Files

```json
{
  "permissions": {
    "defaultMode": "default",
    "allow": [
      "Bash(npm test)",
      "Bash(npm run lint)",
      "Read(/src/**)",
      "Read(/test/**)"
    ],
    "deny": [
      "Bash(rm:*)",
      "Bash(curl:*)",
      "Bash(wget:*)",
      "Edit(.git/**)",
      "Edit(/.codebuddy/**)"
    ]
  }
}
```

### CI / Pipeline Scenario: Skip Approval + Strong Deny

```json
{
  "permissions": {
    "defaultMode": "bypassPermissions",
    "deny": [
      "Bash(rm -rf /:*)",
      "Bash(sudo:*)",
      "Bash(curl * -o /etc/*)",
      "WebFetch(domain:internal-corp.example)"
    ]
  }
}
```

### Team Shared + Personal Relaxation

`<repo>/.codebuddy/settings.json` (committed to git):

```json
{
  "permissions": {
    "deny": ["Bash(rm:*)", "Edit(.git/**)"]
  }
}
```

`~/.codebuddy/settings.json` (user-level, private):

```json
{
  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": ["Bash(git:*)", "Bash(npm:*)"]
  }
}
```

## Related Resources

- [Permission Modes](permission-modes.md): default / acceptEdits / plan / bypassPermissions / delegate and other modes
- [Settings Configuration](settings.md): complete configuration fields, scopes, and merge rules
- [Hooks system](hooks-guide.md): make programmatic permission decisions with hooks
- [Bash sandboxing](bash-sandboxing.md): OS-level isolation for Bash commands
- [CLI Reference](cli-reference.md): startup arguments such as `--allowedTools` / `--disallowedTools` / `--add-dir`
- [Security](security.md): overall security model and best practices
- [IAM Identity and Access](iam.md): organization-level identity authentication and permission control
