14. Tool permission policy
[!ref] Full details in the User Manual, "Tool permissions and approval".
14.1 Three-state semantics
| State | Icon | Behaviour |
|---|---|---|
| Allow | Pass through; no modal | |
| Ask | Modal on every call (wired into the real confirmation flow; the call pauses until approved) | |
| Deny | Reject directly; logged to error drawer |
The three states apply to sub-agents as well: "Deny" takes effect on both the main agent and sub-agents (fail-closed on the sub-agent side — the tool is simply not visible); uninstalling a plugin / removing an MCP backend auto-cleans leftover tool-config keys, keeping on-disk config in line with the tools actually available.
14.2 Decision aggregation and multi-device response
Hook decision aggregation — when several hooks return permission decisions within one evaluation, they merge by (first wins):
deny > defer > ask > allow
Multi-device response — when the desktop dialog and a remote channel (WeChat etc.) receive the same request, the first response wins; later replies from other devices are reported as already answered.
14.3 Always-allow granularity
| Granularity | Coverage | When to use |
|---|---|---|
| Once only | Single call | Cautious evaluation |
| Same tool + same args | One specific call shape | Recommended default |
| Same tool + any args | Any call to this tool | Internal scripts |
| Entire workspace | Every tool in this workspace | Isolated environments only |
14.4 Remote-approval reply
| Command | Equivalent desktop action |
|---|---|
/approve <id> |
Allow once |
/always <id> |
Always allow |
/deny <id> |
Deny |
| No reply | Times out as deny when the 30 s countdown ends |
14.4.1 Unrestricted mode (advanced)
Shield switch to the right of the mode slider; per-session, in-memory only, cleared on restart. 5-second cooling-off on open.
| Removed | When unrestricted |
|---|---|
| Tool approval | Skip the approval modal; execute directly |
| Command allow-list | fs.exec unrestricted |
| Network filter | web.fetch / web.bing don't filter internal / loopback / link-local |
Hard guards still apply (unaffected by the switch):
| Hard guard | Description |
|---|---|
samples/ sandbox |
Cannot be lifted |
rm -rf arg validation |
Still validates |
| Injection deny-list | Fork bombs / /etc writes / curl|sh / eval |
| Protocol allow-list | Only http(s) |
Message marking: the consecutive messages produced while the mode is on are enclosed as a whole by a light border (start / end boundary marks); toggling off closes it on the spot, and no "end" mark is drawn while the mode is still on. Sub-agents can recursively inherit the mode on demand; messages produced under inheritance carry the same marking.
14.5 Approval request fields and persistence
Fields carried by the approval dialog (hook:ask event):
| Field | Description |
|---|---|
request_id |
Approval id |
event |
Triggering hook event name |
fingerprint |
Authorization fingerprint ("Always allow" matches on it) |
handler_type / handler_label |
Handler type and display name |
matcher / match_target |
Matched matcher and match target |
source_scope / source_id |
Source scope (plugin / user / workspace) and its id |
session_id |
Session id |
timeout_sec |
Countdown seconds (30) |
Decision values: deny / allow_once / always_allow.
Persistence — only "Always allow" is persisted: it is written to the workspace-level hook-approvals.yaml (authorization fingerprint + decision time), viewable and revocable under Settings → Hooks. "Allow once", "Deny" and timeouts affect only the current call and are not stored.
