Permission policy export
Superseded (2026-10-21):
hub-permission/1, the scope role presets and the per-harness permission exports were retired with the old server. There are now two permission roles, user and administrator, which govern only the use and publishing of task packs; tool categories follow the MCP annotations. See R01 and D06 in the generic model. Kept as a record only.
This is item 4 of harness neutrality. Permission policy is not a second editable Asset. hub-permission/1 is the neutral JSON computed from the role presets on a scope and from Tool annotations. An adapter only reads it and writes one harness’s configuration. Changing a role or an annotation produces a new export. The export approves nothing.
Runtime permission stays the first step of the Enforcement chain: whether this identity may execute in this scope. allow / ask / deny in a harness is the same person allowing, asking again, or refusing inside a local session. It is not an Independent reviewer, not a Change set, and not a Binding. A Record does not say hub approved.
Inputs
| Input | Source | Use |
|---|---|---|
| Role | Presets on a scope: administrator, member, guest | Who can see this Tool |
twoPersonRule · singleAdminMode |
Scope policy | Recorded only. Not translated into a tool rule |
| Tool annotations | effect (read / write), minimumRole, approval on the provider’s provides |
Compute allow / ask / deny |
Role order: guest < member < administrator. minimumRole is the lowest role that can see the Tool.
For a caller role R and a Tool T:
- R is below
minimumRole→ deny. The catalog does not offer the Tool. - Otherwise
approvalis true → ask. A harness with no card must make the operator confirm these parameters. That is still the same person, not an Independent reviewer. - Otherwise → allow.
effect: writewithapprovalfalse is also allow: a person already decided who may use it, in the Change set. A run does not approve again.
A service client has only a scope string and no role. It is not in this export.
hub-permission/1
| Field | Values | Meaning |
|---|---|---|
schema |
hub-permission/1 |
Fixed |
scopeId |
scp_ prefix |
Which scope |
twoPersonRule |
boolean | Copied from the scope policy. An adapter must not turn it into a tool rule |
singleAdminMode |
boolean | Same |
roleOrder |
guest · member · administrator |
The order above |
tools[].name |
<provider>.<tool> |
Same name as the MCP tool |
tools[].effect |
read · write |
Hub annotation |
tools[].minimumRole |
guest · member · administrator |
Hub annotation |
tools[].approval |
boolean | When true, parameters must be confirmed |
Disposition is not stored. allow / ask / deny for each role is computed by the three rules above, so it cannot drift from the presets.
Example scope scp_example, twoPersonRule on, not single-administrator. Three synthetic Tools:
{
"schema": "hub-permission/1",
"scopeId": "scp_example",
"twoPersonRule": true,
"singleAdminMode": false,
"roleOrder": ["guest", "member", "administrator"],
"tools": [
{
"name": "documents.search",
"effect": "read",
"minimumRole": "guest",
"approval": false
},
{
"name": "buildsite.command_check",
"effect": "read",
"minimumRole": "member",
"approval": false
},
{
"name": "case-gateway.dispatch",
"effect": "write",
"minimumRole": "member",
"approval": true
}
]
}
Computed disposition:
| Tool | Guest | Member | Administrator |
|---|---|---|---|
documents.search |
allow | allow | allow |
buildsite.command_check |
deny | allow | allow |
case-gateway.dispatch |
deny | ask | ask |
Every harness below uses this one policy. One configuration file carries one role. These products’ project files are one policy for everyone in the folder. They have no per-scope role field.
Cannot be expressed, in every harness
| Hub rule | Why |
|---|---|
| Two-person rule, a sole administrator’s own decision, who needs approval | Eligibility on a Change set, not a pass/fail on one tool call |
| Binding, Independent reviewer | A harness prompt asks the same person |
| targets / readers | Visibility is computed in the hub catalog, not in a local permission file |
Enforcement chain, Error codes, executionIsChange |
Verification is a Hook and a Platform invariant. This export does not restate the chain |
| A service client’s scope string | The file describes the operator, not a machine identity |
A call the Enforcement chain must reject is not allowed because a harness file allowed it. hub still runs permission, Binding, Verification, lease, and audit on the call.
Circle
Circle does not write a second permission file. The catalog is computed for the current identity (provider running, role ≥ minimumRole, readers allow). A Tool with approval true uses a card. A local permission prompt, if one still appears, only repeats the confirmation for the same identity. It does not add a Decision.
Claude Code
Read on 2026-10-13:
- Configure permissions:
permissions.allow/ask/deny. A rule isToolorTool(specifier). Evaluation order is deny, then ask, then allow. A broad deny is not opened by a narrower allow. - Hooks reference: an MCP tool name is
mcp__<server>__<tool>.PreToolUsemay returnpermissionDecision: "deny". The permission evaluation described with that hook: a hook allow does not skip deny and ask. - Settings reference and the permissions page: when settings load, an
mcp__rule that contains parentheses is skipped.
| Hub | Lands on | How to write it |
|---|---|---|
| allow | permissions.allow |
mcp__hub__<tool> with no parentheses |
| ask | permissions.ask |
Same |
| deny | permissions.deny |
Same. A bare tool name is removed from context |
effect |
Cannot be expressed | allow / ask / deny do not distinguish read from write. Do not rewrite write as a Bash or file rule |
| Role, scope | Cannot be expressed | One settings.json applies to everyone. One file per role, chosen by the operator. Do not pack three roles into one file |
| Parameter confirmation | Cannot be expressed | ask confirms this tool call, not a Binding. Do not write a PreToolUse hook as a stand-in for the Enforcement chain |
defaultMode, bypassPermissions, and auto are not exported. They are a session baseline, not a role.
Member:
{
"permissions": {
"allow": [
"mcp__hub__documents.search",
"mcp__hub__buildsite.command_check"
],
"ask": ["mcp__hub__case-gateway.dispatch"],
"deny": []
}
}
A guest puts the last two names in deny and leaves only mcp__hub__documents.search in allow. An administrator matches the member: the Tool with approval true stays ask.
The . in a tool name stays in the mcp__ rule. The permissions page does not say whether a dot is legal. When the adapter is delivered, check with that page’s doctor. If it does not match, leave the Tool as cannot be expressed. Do not rename it and pretend it is the same Tool.
Codex CLI
Read on 2026-10-13:
- Agent approvals and security:
sandbox_modeisread-only,workspace-write, ordanger-full-access.approval_policyuseson-requestornever.untrustedis no longer accepted.workspace-writehas no network unless[sandbox_workspace_write] network_accessis set. - Configuration reference:
approval_policymay also be agranulartable.mcp_servers.<id>.enabled_toolsis an allow list.disabled_toolsis a deny list applied after it.default_tools_approval_modeandtools.<tool>.approval_modetakeauto,prompt,writes, orapprove. - MCP: only
writesis defined. It prompts for tools that are not marked read-only.auto,prompt, andapprovehave no behavior defined on that page.
| Hub | Lands on | How to write it |
|---|---|---|
| deny | disabled_tools |
The tool name. Do not also put it in enabled_tools |
| ask | tools.<tool>.approval_mode = "prompt" |
The value is listed. That page does not define how prompt behaves; it only defines writes. Check the page again when the adapter is delivered |
allow and effect: read |
approval_mode = "auto" |
Same: auto is undefined. Do not write read as sandbox_mode = "read-only" |
allow and effect: write |
Cannot be expressed | writes prompts for a tool that is not marked read-only. That is an extra prompt, not the Enforcement chain. No defined value means “allow a write and do not ask the operator again” |
sandbox_mode · approval_policy |
Cannot be expressed | They govern the local command sandbox and when a command pauses. They are not a Tool’s effect or a role |
| Endpoint, credential | Not written | Those belong to hub-provider/1, not to this export |
The examples contain only tool policy. No url, command, or token.
Member and administrator (auto is not used for case-gateway.dispatch; see the table):
[mcp_servers.hub]
enabled_tools = [
"documents.search",
"buildsite.command_check",
"case-gateway.dispatch",
]
disabled_tools = []
[mcp_servers.hub.tools."documents.search"]
approval_mode = "auto"
[mcp_servers.hub.tools."buildsite.command_check"]
approval_mode = "auto"
[mcp_servers.hub.tools."case-gateway.dispatch"]
approval_mode = "prompt"
Guest:
[mcp_servers.hub]
enabled_tools = ["documents.search"]
disabled_tools = ["buildsite.command_check", "case-gateway.dispatch"]
[mcp_servers.hub.tools."documents.search"]
approval_mode = "auto"
case-gateway.dispatch is not marked as a proven allow for member or guest. A member uses prompt because approval is true. The effect: write half of that Tool still cannot be expressed.
OpenCode
Read on 2026-10-13. Permissions (v2) is the current page. Rules sit in the permissions array. Each rule has action, resource, and effect (allow / deny / ask). The last match wins. No match means ask. An MCP action is <server>_<tool>. Unsupported characters in either name become _. The page does not list which characters are unsupported.
The v1 page still describes a permission object whose keys are built-in tool names (read, edit, bash, and so on). It has no MCP tool key. v1 cannot express a hub Tool. The export writes v2 only.
| Hub | Lands on | How to write it |
|---|---|---|
| allow / ask / deny | effect |
One rule, resource * |
| Tool name | action |
hub_documents_search, hub_buildsite_command_check, hub_case_gateway_dispatch. . and - are replaced under “unsupported characters become _”. On delivery, compare with the action OpenCode actually registers. If they differ, that Tool cannot be expressed |
| Role | Cannot be expressed | A global array plus an agent array. No scope role. One file, one role |
effect: read/write |
Cannot be expressed | v2’s effect is allow / deny / ask, not read or write |
The default policy includes { "action": "*", "resource": "*", "effect": "allow" }. A later rule overrides it, so deny and ask must be written. Omitting them does not deny.
Member and administrator:
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "hub_documents_search", "resource": "*", "effect": "allow" },
{ "action": "hub_buildsite_command_check", "resource": "*", "effect": "allow" },
{ "action": "hub_case_gateway_dispatch", "resource": "*", "effect": "ask" }
]
}
A guest sets effect on the last two rules to deny.
Gemini CLI
Read on 2026-10-13:
- Policy engine (footer says Apr 30, 2026): a
[[rule]]hastoolName,decision(allow/deny/ask_user),priority, and optionalmcpName. The highest priority wins. In a non-interactive run,ask_useris treated as deny. The workspace tier is marked disabled. User policy is~/.gemini/policies/*.toml. - The top of that page: since 2026-06-18, Gemini CLI for unpaid and Google One users has been replaced by Antigravity CLI. The text below describes only the Gemini CLI policy engine that page still documents. It does not describe Antigravity.
- Configuration:
general.defaultApprovalModeisdefault,auto_edit, orplan.yolocan be turned on only from the command line. That is a session baseline and is not exported.
| Hub | Lands on | How to write it |
|---|---|---|
| allow | decision = "allow" |
mcpName = "hub", toolName is the hub name |
| ask | decision = "ask_user" |
Only an interactive session asks again. Non-interactive becomes deny, and the export note must say so |
| deny | decision = "deny" |
User-tier priority 100, above the built-in defaults |
| Role, shipping with a repository | Cannot be expressed | The workspace tier is disabled. One user file, one role |
effect |
Cannot be expressed | decision has no read / write |
Member and administrator:
[[rule]]
mcpName = "hub"
toolName = "documents.search"
decision = "allow"
priority = 100
[[rule]]
mcpName = "hub"
toolName = "buildsite.command_check"
decision = "allow"
priority = 100
[[rule]]
mcpName = "hub"
toolName = "case-gateway.dispatch"
decision = "ask_user"
priority = 100
A guest sets decision on the last two rules to deny.
Cursor
Read on 2026-10-13:
- permissions.json:
mcpAllowlistis a list ofserver:toolstrings. There is no deny list. An MCP tool left off the list is not refused because of that.autoRunonly steers the classifier in Auto-review. The page says it is not enforcement. - Run Modes: in Allowlist mode, listed calls run and the rest ask. Run Mode is a setting, not a field of
permissions.json. The same page says that Auto-review is not a security boundary; it makes no such statement about Allowlist mode.
| Hub | Lands on | How to write it |
|---|---|---|
| allow | mcpAllowlist |
hub:<tool>, for example hub:documents.search |
| ask | Leave it off the list | Only when Run Mode is Allowlist: a missing entry asks. Auto-review sends the rest to a classifier, which is not ask |
| deny | Cannot be expressed | There is no deny list. Omitting a tool asks, or hands it to the classifier. It does not remove the tool from the catalog |
| Role | Cannot be expressed | The user file and the repository file concatenate arrays. They do not switch by role |
effect · sandbox |
Cannot be expressed | sandbox.json governs local read, write, and network. It is not a hub Tool |
Member and administrator (Allowlist mode; ask is the omission):
{
"mcpAllowlist": [
"hub:documents.search",
"hub:buildsite.command_check"
]
}
A guest can only write:
{
"mcpAllowlist": ["hub:documents.search"]
}
buildsite.command_check and case-gateway.dispatch are then not auto-run, and they are not denied. For a guest those two Tools cannot be expressed.
Check when an adapter is delivered
- The exported disposition matches the three rows above. Do not turn ask into allow to avoid a prompt.
- A cell that cannot be expressed stays unwritten. Do not add a nearby switch as a substitute.
- The synthetic acceptance checks file shape and rule count. It does not connect to a real harness and does not call a paid model.
This document is one of circle-hub’s design specifications, licensed under CC BY 4.0: quote or build on it with attribution to Circle Hub and qingshanfeihu and a link to the original. See the design specifications index.