03. Permissions & Policies¶
🎯 Objective¶
Control what the agent can do with permissions (per agent, with ask/deny) and policies (binary, never ask, only restrict).
🧩 Permissions vs Policies¶
Permissions (permissions) |
Policies (policies) |
|
|---|---|---|
| Effects | allow, ask, deny |
allow, deny (never asks) |
| Scope | Rules per action/tool | Can remove a provider/MCP/skill/plugin |
| Role | Decides the agent's flow | Tightens what permissions would allow |
📋 Permissions — ordered rules¶
{
"permissions": [
{ "action": "shell", "resource": "git push *", "effect": "ask" },
{ "action": "read", "resource": ".env*", "effect": "deny" }
]
}
- The last matching rule wins → broad rules first, exceptions after
*matches zero or more characters;?matches one
Common actions¶
| Action | Covers |
|---|---|
shell |
Shell commands |
edit |
Edit/create/patch |
read, glob, grep |
Local discovery |
subagent |
Child agents |
webfetch, websearch |
Web tools |
skill |
Loading skills |
external_directory |
Access outside the project/worktree |
⚠️ In V2,
external_directoryis allowed by default (unlike V1). To require approval:{ "action": "external_directory", "resource": "*", "effect": "ask" }.
Per agent¶
The same rules can live under agents.<id>.permissions to apply to a single agent.
🛡️ Policies — binary restriction¶
{
"policies": [
{ "action": "provider.use", "resource": "*", "effect": "deny" },
{ "action": "provider.use", "resource": "anthropic", "effect": "allow" },
{ "action": "tool.use", "resource": "shell:git push *", "effect": "deny" },
{ "action": "integration.use", "resource": "mcp:*", "effect": "deny" }
]
}
| Action | Resource | Removes |
|---|---|---|
provider.use |
Provider ID | Provider (disappears from the catalog, even with credentials) |
tool.use |
<action>:<value> |
A specific permission check |
integration.use |
mcp:<name>, skill:<id>, plugin:<pkg> |
An integration |
Precedence (policies invert the normal rule)¶
Broader configuration is evaluated last, overriding specific rules:
- Connected Console (highest authority)
- macOS managed preferences
- System (
/etc/opencode) - Global (
~/.config/opencode) - Direct
opencode.json .opencode/opencode.json(lowest)
In other words: a repository cannot re-enable a provider you denied globally.
~/.config/opencode/opencode.jsonc
{ "policies": [{ "action": "provider.use", "resource": "openai", "effect": "deny" }] }
❓ Hands-on challenge¶
- Force
askongit push *and check that the push asks for approval - Deny reading
.env*and confirm the agent cannot read it - Create a policy that denies
shell:*for a review agent and allows onlyshell:git status * - Deny a provider via policy and see it disappear from
/models
📚 Official source¶
Next step: Lesson 04 — Tools · Back to module