Permissions and consent
Docs/Use/Permissions and consent

Permissions and consent

Edit on GitHub
docs-site/use/permissions.md

Every tool call is checked by the policy engine before it runs, for every harness and every extension alike. Rules say what is allowed, what is denied, and what to ask you about.

Rules

[permissions]
deny  = ["creds(*)"]
ask   = ["write(./**)", "spawn(*)", "net(domain: *)"]
allow = ["read(./**)"]
  • deny is walked first, from every layer. Nothing carves an exception out of a deny, and a managed deny cannot be relaxed.
  • ask puts the call to you (see consent below).
  • allow lets it run.

A rule names an aspect and a scope:

RuleMatches
read(./src/**)Reading paths under src/ in the workspace.
write(./docs/**)Writing paths under docs/.
spawn(cmd: git)Starting git.
net(domain: docs.rs)Network requests to docs.rs.
creds(*)Reading any credential.
tool(builtin.bash)Calling one tool by name.
ui(pane.right)Putting a surface in the right pane. Also ui(band), ui(inline), ui(footer), ui(pane.left), ui(hidden), or ui(*) for all.

Placements. A ui(...) rule decides where an agent or an extension may put a surface. With no rule, a placement is allowed. An unknown placement name fails at start and lists the valid ones. An ask rule on a placement is refused when the call is made, because a placement cannot wait for an answer: use allow or deny. See Show things: placements.

The floor. With no rules at all, the agent may read, write and spawn inside the workspace and call every registered tool: tool(*), read(./**), write(./**), spawn(*). A path outside the workspace root does not match ./**, so it is refused.

Per-action keys

For the common cases, one word each:

[permissions]
shell   = "ask"     # tool(builtin.bash)
network = "deny"    # net(domain: *)
commit  = "allow"   # tool(git.commit)
push    = "ask"     # tool(git.push)
pr      = "ask"     # tool(git.pr)

Each takes allow, ask or deny. They mix with the lists.

Ask why

orrery permissions explain "write(./src/main.rs)"
orrery permissions explain "net(domain: docs.rs)" --subject ext:my-ext
read(./notes.txt) for agent: Allow — `read(./**)` in the allow list
  by allow `read(./**)` [Project] <built-in default>:2

It names the rule, the layer, the file and the line. The argument is one call, not a pattern. --subject is agent (the default), agent:<name> for a sub-agent, or ext:<id> for an extension.

When a call matches an ask rule, the run waits and a consent bar appears above the input:

KeyAnswer
yAllow once.
aAllow always, for this session.
nDeny.
dDeny always, for this session.
eShow the whole request.

A prompt nobody answers is denied after 60 seconds. Any attached terminal or client that can draw the consent bar may answer; the first answer wins.

What an ask rule does is the consent mode:

ModeEffectDefault
onceAsk the person.At a terminal and under serve.
alwaysAllow without asking.Never.
neverDeny without asking, so CI cannot hang.Under run.

Set it per run with --consent <once|always|never>, or in a file as consent = "..." at the top level or in [profile.<name>]. The flag beats the profile. A managed consent is a ceiling neither can lift.

If no attached client can answer, the call is denied at once, and the message says why.

The record

Every decision is audited, allowed or denied:

orrery ledger -n 20                       # the last 20 decisions, and which rule made each
orrery ledger --subject ext:my-ext
orrery ledger --rule "write(./**)"
orrery ledger --stream load               # what loaded, degraded or was refused

A run whose last tool call was denied exits with code 4.