Skip to content

5.2c Agent Permissions & Security

Precisely control what Agents can and cannot do.

📝 Course Notes

Key concepts from this lesson:

Agent Permissions & Security Notes

What You'll Learn

  • Understand the permission system architecture
  • Configure bash/edit/task/skill permissions
  • Design secure Agent systems
  • Implement the principle of least privilege

Permission System Architecture

Three Permission Actions

ActionDescriptionEffect
allowAllowExecute directly, no confirmation needed
askAskShow confirmation dialog, user decides
denyDenyRefuse to execute, Agent receives error

Permission Configuration Hierarchy

Default Permissions (defined in source code)
    ↓ overrides
Global Config permission
    ↓ overrides
Agent-level permission

Later configurations override earlier ones.

Source: packages/core/src/v1/config/permission.ts (schema definition) and packages/opencode/src/agent/agent.ts:145 (Permission.merge call)

Rule Priority: Last Match Wins

This is the most important rule! When multiple rules match, the last matching rule wins.

jsonc
{
  "permission": {
    "bash": {
      "*": "ask",           // Rule 1: All commands need confirmation
      "git *": "allow",     // Rule 2: git commands allowed
      "git push*": "deny"   // Rule 3: git push denied
    }
  }
}

Executing git push origin main:

  1. Matches Rule 1 (*) → ask
  2. Matches Rule 2 (git *) → allow
  3. Matches Rule 3 (git push*) → deny
  4. Final result: deny (Rule 3 is last)

Source: agents.mdx:473, permissions.mdx:70


Configurable Permission Types

PermissionMatch TargetDescription
readFile pathRead files
editFile pathAll file modifications (edit/write/patch)
globglob patternFile search
grepRegex patternContent search
listDirectory pathList directory contents
bashCommand stringExecute shell commands
tasksubagent nameCall sub-agents
skillskill nameLoad skills
lsp-LSP queries (currently no fine-grained support)
todowrite-Read and write the todo list (gates the todowrite tool)
webfetchURLFetch web content
websearchQuery stringWeb search
external_directory-Access paths outside project directory
doom_loop-Detect repeated calls (same tool called 3 times with same input)
question-Ask the user a question (defaults to deny to prevent subagents from interrupting the user)
plan_exit-Exit Plan mode and switch to the build agent

Source: packages/core/src/v1/config/permission.ts:17-36


Permission Configuration Syntax

Simple Syntax: Single Action

jsonc
{
  "permission": {
    "edit": "allow",      // All file edits allowed
    "bash": "ask",        // All commands need confirmation
    "webfetch": "deny"    // Web fetching denied
  }
}

Global Setting

jsonc
{
  "permission": "allow"   // All permissions allowed
}

Object Syntax: Fine-grained Control

jsonc
{
  "permission": {
    "bash": {
      "*": "ask",              // Default: need confirmation
      "git status": "allow",   // git status allowed
      "git log*": "allow",     // git log commands allowed
      "rm -rf*": "deny"        // rm -rf denied
    }
  }
}

Wildcards

SymbolMeaningExample
*Match any characters (0 or more)git * matches git status, git log
?Match single characterfile?.txt matches file1.txt

bash Permission Details

The bash permission matches the parsed command string.

Common Configuration

jsonc
{
  "permission": {
    "bash": {
      "*": "ask",                    // Default: need confirmation

      // Git commands
      "git status": "allow",
      "git log*": "allow",
      "git diff*": "allow",
      "git branch*": "allow",
      "git checkout*": "ask",        // Branch switch needs confirmation
      "git push*": "ask",            // Push needs confirmation
      "git reset --hard*": "deny",   // Hard reset denied

      // Package management
      "npm install*": "allow",
      "npm run*": "allow",
      "npm publish*": "deny",        // Publishing denied

      // Dangerous commands
      "rm -rf*": "deny",
      "sudo*": "deny",
      "chmod 777*": "deny"
    }
  }
}

Best Practice for Plan Agent

jsonc
{
  "agent": {
    "plan": {
      "permission": {
        "bash": {
          "*": "deny",               // Default: deny all
          "git log*": "allow",       // Read-only commands allowed
          "git diff*": "allow",
          "git status": "allow",
          "ls*": "allow",
          "cat*": "allow",
          "head*": "allow",
          "tail*": "allow"
        }
      }
    }
  }
}

edit Permission Details

The edit permission controls all file modification operations, including:

  • edit tool
  • write tool
  • patch tool

Common Configuration

jsonc
{
  "permission": {
    "edit": {
      "*": "allow",                    // Default: allow

      // Sensitive files
      "*.env": "deny",
      "*.env.*": "deny",
      "*.env.example": "allow",        // Example files allowed
      ".env.local": "deny",

      // System files
      "package-lock.json": "deny",     // Don't modify lock files
      "pnpm-lock.yaml": "deny",
      "yarn.lock": "deny",

      // Directories
      "node_modules/*": "deny",
      ".git/*": "deny",
      "dist/*": "deny"
    }
  }
}

Read-only Agent Configuration

jsonc
{
  "agent": {
    "readonly-auditor": {
      "description": "Read-only code audit, no file modifications",
      "mode": "subagent",
      "permission": {
        "edit": "deny"                 // Deny all edits
      }
    }
  }
}

task Permission: Control Subagent Calls

The task permission controls which subagents an Agent can call.

How It Works

When task: deny is set:

  1. The subagent is completely removed from the Task tool's description
  2. The model won't attempt to call it (because it can't see it)

Note: Users can still manually call any subagent via @agent-name. The task permission only affects automatic Agent calls.

Source: agents.mdx:557-565

Configuration Example

jsonc
{
  "agent": {
    "safe-orchestrator": {
      "description": "Security orchestrator, can only call specified subagents",
      "mode": "primary",
      "permission": {
        "task": {
          "*": "deny",                   // Deny all
          "docs-writer": "allow",        // Allow docs
          "code-reviewer": "allow",      // Allow review
          "dangerous-agent": "deny"      // Explicitly deny
        }
      }
    }
  }
}

Using Wildcards

jsonc
{
  "agent": {
    "orchestrator": {
      "permission": {
        "task": {
          "*": "deny",
          "safe-*": "allow",            // All safe-* allowed
          "internal/*": "allow",        // Nested directory allowed
          "code-reviewer": "ask"        // Needs confirmation
        }
      }
    }
  }
}

TaskTool Parameter Details

The complete parameter definition for the Task tool:

ParameterTypeRequiredDescription
descriptionstringYesTask description (3-5 words), used as sub-session title
promptstringYesTask prompt for the sub-agent to execute
subagent_typestringYesSub-agent name to call (must be non-primary agent)
task_idstringNoResume a previous task; pass its returned task_id to reuse the same subagent session
commandstringNoCommand that triggered this task (for debugging)
backgroundbooleanNoRun in the background; requires OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true

Execution Flow

The TaskTool workflow:

Main Agent (Build)
    ↓
    1. Permission Check
       - Check if caller has task permission
       - Filter accessible subagents
       ↓
    2. Create Sub-session
       - Create independent session under main session
       - Title: description + (@subagent subagent)
       - Use the subagent's own permissions while inheriting the parent session's deny and external_directory rules
       ↓
    3. Call Sub-agent
       - Sub-agent executes in independent session
       - Context only contains passed prompt
       - Listen to PartUpdated events for progress
       ↓
    4. Return Result
       - Collect all tool call summaries
       - Generate conversation summary
       - Return to Main Agent

Key Point: Sub-agents run in independent Sessions and cannot see the Main Agent's conversation history. You must provide complete context when calling.

Background Execution and Nesting Depth

Background subagents remain experimental. Once enabled, the Task tool accepts background: true. The TUI can also move a synchronous subagent from the currently blocked session into the background. When it finishes, the result is automatically reported to the parent session—do not poll for progress.

bash
export OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true

The top-level subagent_depth setting controls subagent nesting depth. Its default is 1, which means a subagent cannot create another subagent by default. To allow nesting, raise this value and explicitly configure task in the relevant subagent's own permissions:

jsonc
{
  "subagent_depth": 2
}

Source: runtime-flags.ts:43, task.ts:43-61, task.ts:96-117, and config.ts:84-86

Practical Usage Examples

Configure to Allow Specific Sub-agents

jsonc
{
  "agent": {
    "orchestrator": {
      "description": "Task orchestration Agent, can call specialized sub-agents",
      "mode": "primary",
      "permission": {
        "task": {
          "docs-writer": "allow",      // Allow docs writing
          "code-reviewer": "allow",    // Allow code review
          "general": "allow",          // Allow general tasks
          "*": "deny"                  // Deny others
        }
      }
    }
  }
}

Agent Internal TaskTool Call

markdown
# Pseudo-code example
Main Agent receives: Help me write API documentation

1. Analyze task type → Determine docs-writer sub-agent is needed
2. Call TaskTool:
   - description: "Write API documentation"
   - prompt: "Write documentation for the following functions..."
   - subagent_type: "docs-writer"
3. Sub-agent executes → Returns documentation content
4. Main Agent receives result → Continues conversation

Resume a Task

When a subagent needs to execute in steps, pass the task_id returned by the previous call to continue its work:

TaskTool(
  description: "Complete documentation",
  prompt: "Check documentation completeness and fill in missing content",
  subagent_type: "docs-writer",
  task_id: "abc123"  // Continue the previous task
)

Source: packages/opencode/src/tool/task.ts:43-172


skill Permission: Control Skill Loading

The skill permission controls which skills an Agent can load.

Configuration Example

jsonc
{
  "agent": {
    "restricted-agent": {
      "description": "Restricted Agent, can only use specified skills",
      "mode": "subagent",
      "permission": {
        "skill": {
          "*": "deny",                   // Deny all skills
          "docs-writer": "allow",        // Only allow docs skill
          "translator": "allow"
        }
      }
    }
  }
}

Source: skill.ts:15-21


Built-in Security Rules

OpenCode has some default security rules configured:

.env File Protection

jsonc
// Built-in default configuration
{
  "permission": {
    "read": {
      "*": "allow",
      "*.env": "ask",           // .env files require confirmation (for security)
      "*.env.*": "ask",         // .env.xxx files also require confirmation
      "*.env.example": "allow"  // Example files allowed
    }
  }
}

Source: agent.ts:130-135

doom_loop Detection

When the same tool is called 3 times consecutively with identical input, doom_loop detection is triggered.

jsonc
{
  "permission": {
    "doom_loop": "ask"    // Default: prompt user for confirmation
  }
}

question Permission

Controls whether an Agent can use the question tool to ask the user a question.

DefaultDescription
subagent: denyPrevents subagents from interrupting the user unnecessarily
build agent: allowAllows the primary Agent to ask questions

Use case: Set this permission to allow when a subagent needs to confirm uncertainties with you.

jsonc
{
  "agent": {
    "interactive-helper": {
      "permission": {
        "question": "allow"    // Allow this subagent to ask questions
      }
    }
  }
}

Source: agent.ts:126 and question.ts

external_directory Protection

When an Agent attempts to access paths outside the project directory:

jsonc
{
  "permission": {
    "external_directory": "ask"    // Default: prompt user for confirmation
  }
}

plan_enter / plan_exit Permissions

Control whether an Agent can switch Plan mode:

  • plan_enter: Enter Plan mode. It remains available as a permission key, but there is no corresponding tool implementation in the source; the user enters it by pressing Tab to switch to the plan agent.
  • plan_exit: Exit Plan mode and switch to the build agent. This action is implemented by PlanExitTool.

The target version does not implement a plan_enter tool, so retaining a permission key with that name does not let the Build Agent switch on its own. The Plan Agent uses plan_exit to request a return to Build, and the request is evaluated by the normal permission rules.

jsonc
{
  "agent": {
    "plan": {
      "permission": {
        "plan_exit": "allow"      // Allow the Plan Agent to request a return to Build
      }
    }
  }
}

Source: plan.ts and registry.ts

Deprecated Field: tools

⚠️ The tools field is deprecated. Use permission instead.

Old syntax (deprecated):

jsonc
{
  "agent": {
    "my-agent": {
      "tools": {
        "bash": false,      // Disable bash
        "edit": true        // Allow editing
      }
    }
  }
}

New syntax:

jsonc
{
  "agent": {
    "my-agent": {
      "permission": {
        "bash": "deny",     // Disable bash
        "edit": "allow"     // Allow editing
      }
    }
  }
}

Migration notes:

  • write, edit, and patch in tools map to the edit permission.
  • Other legacy tool names may be converted into permission keys with the same name, but they do not create callable capabilities when the target version has no corresponding tool.
  • true becomes "allow"; false becomes "deny".
  • OpenCode converts old configurations automatically, but you should update them manually.

Source: packages/core/src/v1/config/agent.ts:71-76

Subagent Permission Inheritance

When the Task tool creates a child session, its capabilities start with the subagent's own permissions; it does not inherit the parent Agent's allow or ask rules. To prevent the subagent from bypassing the parent session's security boundary, it does inherit every deny rule and every external_directory rule from the parent session.

Two additional denials are applied by default:

  • If the subagent's own rules do not configure todowrite, OpenCode appends todowrite: deny.
  • If the subagent's own rules do not configure task, OpenCode appends task: deny.

These are defaults, not hardcoded restrictions that can never be overridden. Explicitly configuring the corresponding permissions still requires the call to satisfy subagent_depth. Tools listed in experimental.primary_tools continue to receive an appended deny rule in Task child sessions.

Source: subagent-permissions.ts:4-26 and task.ts:139-170


Agent-level Permission Override

Permissions set in Agent configuration override global permissions.

JSON Configuration

jsonc
{
  "permission": {
    "bash": {
      "*": "ask",
      "git status": "allow"
    }
  },
  "agent": {
    "build": {
      "permission": {
        "bash": {
          "git push": "allow"       // build agent additionally allows push
        }
      }
    },
    "plan": {
      "permission": {
        "bash": {
          "*": "deny",              // plan agent denies all commands
          "git log*": "allow"       // except viewing logs
        }
      }
    }
  }
}

Markdown Configuration

markdown
---
description: "Read-only audit Agent"
mode: "subagent"
permission:
  edit: "deny"
  bash:
    "*": "deny"
    "git log*": "allow"
    "git diff*": "allow"
  webfetch: "deny"
---

Only analyzes code, makes no modifications.

Security Best Practices

1. Principle of Least Privilege

Only grant the minimum permissions an Agent needs to complete its task.

jsonc
// ❌ Bad: Too permissive
{
  "agent": {
    "my-agent": {
      "permission": "allow"
    }
  }
}

// ✅ Good: Explicitly list needed permissions
{
  "agent": {
    "my-agent": {
      "permission": {
        "read": "allow",
        "edit": {
          "docs/*": "allow"
        },
        "bash": "deny"
      }
    }
  }
}

2. Explicitly List Allowed Commands

jsonc
// ❌ Bad: Allow all, then deny dangerous ones
{
  "permission": {
    "bash": {
      "*": "allow",
      "rm -rf*": "deny"
    }
  }
}

// ✅ Good: Deny all, then allow needed ones
{
  "permission": {
    "bash": {
      "*": "deny",
      "git status": "allow",
      "npm test": "allow"
    }
  }
}

3. Set Sensitive Operations to ask

jsonc
{
  "permission": {
    "bash": {
      "*": "allow",
      "git push*": "ask",        // Push needs confirmation
      "npm publish*": "ask",     // Publish needs confirmation
      "docker *": "ask"          // Docker operations need confirmation
    }
  }
}

4. Regularly Review Permission Configuration

Checklist:

  • [ ] Are there permissions no longer needed?
  • [ ] Are all sensitive operations set to ask?
  • [ ] Are new Agent permissions reasonable?

Common Pitfalls

SymptomCauseSolution
Permission not workingWrong rule orderPut * first, specific rules after
Subagent still callableUser @ calls are unrestrictedtask permission only affects Task tool
bash command match failsMatches parsed commandCheck actual command format (with arguments)
.env can be read without confirmationA custom rule overrode the defaultSet .env to ask if you need to protect it
Permissions too strictSet *: deny forgot to allow necessary onesAdd allow rules one by one

Relationship with 5.5 Permission Control

This chapter focuses on Agent-level permission configuration.

For global permission configuration and more details, see 5.5 Permission Control.


Lesson Summary

You learned:

  1. Permission System Architecture: Three actions, configuration hierarchy, last match wins
  2. Common Permission Types: bash, edit, task, skill, question, plan_exit, and more
  3. Fine-grained Control: Using object syntax and wildcards
  4. TaskTool Mechanism: Sub-agent calls, parameter definition, execution flow
  5. Subagent Boundaries: Uses its own permissions, inherits the parent session's deny / external_directory rules, and is limited by the default nesting depth
  6. Built-in Security Rules: .env protection, doom_loop, external_directory
  7. Security Best Practices: Least privilege, explicit allow, sensitive operations ask

Next Lesson Preview

With permissions configured, there are more advanced techniques: tool interface design, pass-through parameters, debugging methods.

Next Lesson: 5.2d Agent Advanced Techniques