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.