Permission-tag reference

Operator·6 minutes to read

The eleven tags a tool declares in its manifest's tools[].tags (see docs/reference/module-manifest.md), read directly from packages/core/src/permissions.ts's PERMISSION_TAGS array — scripts/docs-check.mjs checks both directions: every tag in the array has a heading on this page, and every heading on this page is a real tag.

The three sets

A tag can belong to any combination of three sets, and the policy engine reads all three off packages/core/src/permissions.ts:

  • REQUIRES_APPROVAL_BY_DEFAULT — a call tagged with one of these needs a member's approval unless a saved rule already covers it.
  • NEVER_ALWAYS_ALLOW — a saved always-allow rule is never honoured for this tag, whatever it says. A call carrying this tag is approved fresh, every time.
  • TAINT_SENSITIVE — while a run is tainted by untrusted content (it has read something from outside the deployment that could carry an injected instruction), an existing always-allow rule for one of these tags is ignored and the call is approved fresh again — because a rule granted before the run touched anything untrusted is exactly what a prompt injection would try to spend.

The two rules a module author most often gets wrong

A saved rule never covers local execution, whatever it is worded to say. shell is not even in NEVER_ALWAYS_ALLOW — the refusal is a separate, unconditional check in the policy engine (packages/core/src/policy.ts), because that set is for a call that still gets approved every time; a shell call is not merely re-approved every time, it releases nothing a saved rule could possibly cover, ever. The only always-allow path for local execution is the per-machine gate arriving at M7, sitting behind its own automatic reviewer — never a policy rule.

spend is about the member's money in the world, never a credit draw. A virtual card, a purchase — real money leaving the deployment. A model call drawing against an organisation's credit balance is not spend and never routes through the policy engine at all (ADR 0018 §5): a model call is not an approvable action, and wiring the credit meter into decide() would be a category error a module author should never need to notice, because it should never come up.

The eleven tags

read

Reads something and changes nothing — no external call, no write, no cost. Not in REQUIRES_APPROVAL_BY_DEFAULT, NEVER_ALWAYS_ALLOW or TAINT_SENSITIVE: the lightest tag there is. Getting this wrong is almost always under-tagging — marking a tool read when it actually writes, sends or spends — which means a member never gets asked about something that changed or cost something real.

external_read

Reads something from outside the deployment — a connector's answer, a provider's search result. Not in REQUIRES_APPROVAL_BY_DEFAULT or NEVER_ALWAYS_ALLOW, and not in TAINT_SENSITIVE either: on an ordinary run it behaves exactly like read, and a member's own saved rules cover their own reads. It is in CHANNEL_TAINT_SENSITIVE, the taint set a channel-started run is judged against (ruling M5-D7). A channel_bindings row names one member and every inbound message addressed to that handle runs as that member, so anyone who can address the handle drives the bot as somebody they are not; on those runs a read that leaves the deployment is a question for the binding member rather than something a rule saved earlier can release. The runtime adds this tag to a connector or MCP tool on a channel-started run; nothing else attaches it, and a manifest that declares it is describing a tool that reads outside the deployment.

write

Changes something inside the deployment — a database row, a stored file — without sending it anywhere external. In REQUIRES_APPROVAL_BY_DEFAULT and TAINT_SENSITIVE; not in NEVER_ALWAYS_ALLOW, so a member can grant a standing always-allow rule for a specific write they trust. Under-tagging a real write as read is a silent change with no approval ever offered; over-tagging a read as write trains a member to rubber-stamp approvals that never mattered.

external_send

Sends something outside the deployment — an email, a message, a webhook call. In REQUIRES_APPROVAL_BY_DEFAULT and TAINT_SENSITIVE. Under-tagging this is the one members are most likely to actually mind: data leaving with nobody having said yes.

spend

The bot spending the member's money in the world — never a credit draw; see above. In all three sets: approval by default, never covered by a saved rule, and re-checked fresh under taint even if it somehow were. Under-tagging a real purchase tool as write would hand it a shortcut (write is not in NEVER_ALWAYS_ALLOW) that spend is specifically designed never to have.

delete

Deletes something the member would consider theirs. In all three sets, the same as spend — a deletion never gets a standing always-allow rule, and is re-approved fresh under taint. Under- tagging a real delete as write gives it exactly the standing-rule shortcut this tag exists to deny it.

sign_in

The bot authenticating as the member somewhere — spending their identity, not their money. In all three sets. Under-tagging this as external_send (it does leave the deployment, technically) would let a member grant a standing rule for what is actually an impersonation, which NEVER_ALWAYS_ALLOW exists specifically to prevent.

install

Installs something that changes what the bot itself can later do — a package, an extension, a skill. In REQUIRES_APPROVAL_BY_DEFAULT and TAINT_SENSITIVE, not in NEVER_ALWAYS_ALLOW (a member may grant a standing rule for a specific, trusted install). Under-tagging an install as write loses the taint sensitivity: an install approved before a run went near untrusted content would otherwise survive into a run that did.

shell

Local execution — running a command or a script on a machine. In REQUIRES_APPROVAL_BY_DEFAULT and TAINT_SENSITIVE, and functionally never-always-allow even though it is not a member of that set; see above for why the refusal lives elsewhere. Under-tagging local execution as write is the single most consequential mistake on this list: it would make available a shortcut this tag was built, by a dedicated correction, to make impossible.

computer

The bot driving a computer of its own — mouse, keyboard, screen. Not in REQUIRES_APPROVAL_BY_DEFAULT, NEVER_ALWAYS_ALLOW or TAINT_SENSITIVE — the same shape as read in this policy engine, which is not the whole story: computer use gets its own continuous, per-action automatic reviewer (F-COMP-11) rather than a one-time approval prompt, arriving at M7. Tagging a tool computer when it is not driving a computer at all loses that reviewer for something that never needed it, and the reverse loses it for something that did.

decides_about_person

The bot making or materially influencing a decision about a specific person — eligibility, scoring, moderation. In REQUIRES_APPROVAL_BY_DEFAULT and NEVER_ALWAYS_ALLOW, but not TAINT_SENSITIVE — not an oversight: NEVER_ALWAYS_ALLOW already means no standing rule ever exists for this tag to begin with, so there is nothing left for taint-sensitivity to suppress. Under-tagging a decision-about-a-person as write is how a consequential, personal decision ends up with a standing always-allow rule that this tag exists specifically to forbid.

Last verified against build c0f77aa.