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's permission system is not just "look at the current mode" — it is a layered evaluation chain. For each tool call, the determination roughly follows this order:
| Phase | Check | Behavior on match |
|---|---|---|
| 0 | Hooks / interactive tool exceptions | PreToolUse can directly allow / deny / ask; tools like AskUserQuestion inherently require interaction |
| 1 | Deny rules | Reject immediately, highest priority |
| 2 | Trusted Allow rules (user / CLI / session / policy / trusted project rules / --allowedTools) | Allow immediately, and can bypass command safety checks |
| 3 | Command safety check (interactive only) | Force confirmation for high-risk Bash commands |
| 4 | Ask rules | Force ask |
| 5 | Bypass mode short-circuit | bypassPermissions allows most actions here; if disabled, it degrades |
| 6 | Untrusted Allow rules (untrusted project rules, command / sandbox sources) | Can allow, but cannot bypass command safety checks from the previous step |
| 7 | Permission Mode baseline strategy | The current Permission Mode decides whether the default is allow or ask |
| 8 | Non-interactive fallback | When no approval dialog can be shown, converts unresolved ask to deny |
| 9 | dontAsk / auto final catch | dontAsk rewrites ask to deny; auto only handles ask and delegates to the classifier |
Key point 1:
denyalways takes precedence.Key point 2: CodeBuddy splits
allowinto "trusted allow" and "untrusted allow" layers. Before you explicitly trust a project directory,allowrules in the repository's.codebuddy/settings.json/.codebuddy/settings.local.jsoncannot bypass command safety checks. This prevents malicious repositories from silently widening local security boundaries by committing their own settings.Key point 3:
autois not a replacement for the entire chain — it only handles "actions that would still result in ask at the end"; explicitaskrules do not enter the classifier.
Read-only tools (Read / Grep / Glob, etc.) do not prompt for approval by default inside trusted directories. Edit, Bash, and most side-effect tools go through the full evaluation chain.
Which Takes Precedence: Permission Modes or Rules?
The permission system can be understood as two layers:
- Rule layer:
deny/ask/allow - Mode layer:
default/acceptEdits/auto/dontAsk/ ...
In CodeBuddy, rules typically take precedence over modes. The most important examples:
denytakes effect before any mode- Explicit
askrules take effect beforeauto, so manual confirmation is still required dontAskdoes not bypass rules; it only rewrites the finalaskresult todenybypassPermissionsdoes not erase preceding rules;deny/askcan still block it- In interactive sessions, dangerous Bash commands may still enter ask due to safety checks, even under
bypassPermissions
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 approvalask: Ask for approval every time it is useddeny: 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 for details. CodeBuddy merges these 4 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 matchls -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 aspython -c 'open(".git/config", "w")...'. OS-level protection depends on Bash sandboxing - 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 subdomainsSupports 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 |
Web UI add directory (/api/v1/workspace-dirs) | Process-level (persisted by frontend) |
permissions.additionalDirectories setting | Persistent |
permissions.trustedDirectories setting | Persistent |
The effective trusted directory list = workspace root + settings.trustedDirectories + directories added at startup with --add-dir, in-session with /add-dir, or via the Web UI.
Both
--add-dirandpermissions.additionalDirectoriesonly 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:
allowrules in<repo>/.codebuddy/settings.jsonand.codebuddy/settings.local.jsonare 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):
- 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 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:
- The structured
permissionDecisionfromPreToolUsetakes effect before regular permission rules:allow/deny/askdirectly allows, rejects, or forces confirmation respectively - Blocking hooks (exit code 2) can also short-circuit before regular allow rules, so you can "allow all Bash first, but use hooks to separately block a few specific commands"
- If you need unconditional hard boundaries, it is still recommended to prioritize
permissions.denyfor unified auditing in/permissionsand settings
Working with Sandboxing
Permission rules and Bash sandboxing 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:
denyrules 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 sandboxallowedDomainsboth apply, and the final boundary is the intersection
Settings Priority
Permission rules inherit the general Settings priority:
flagSettings / cliArg / session > userSettings > policySettings >
projectSettings > localSettings > command/sandbox sourcesHowever, the evaluation order is more important than settings priority:
denyarrays are merged from all scopes, and a deny in any scope rejectsallowarrays 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 trusteddisableBypassPermissionsModetakes 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: default / acceptEdits / plan / bypassPermissions / delegate and other modes
- Settings Configuration: complete configuration fields, scopes, and merge rules
- Hooks system: make programmatic permission decisions with hooks
- Bash sandboxing: OS-level isolation for Bash commands
- CLI Reference: startup arguments such as
--allowedTools/--disallowedTools/--add-dir - Security: overall security model and best practices
- IAM Identity and Access: organization-level identity authentication and permission control