TypeClawTypeClaw
Reference

Permissions and security guards

Every built-in permission string and every guard's tier

The mental model is at /concepts/permissions-model. This page lists the strings.

Built-in roles

Roles form a strict tower: each role bypasses every guard at its tier and below.

RoleDefault match[]Tier bypass capDefault permissions[]
owner[{ kind: 'tui' }]highchannel.respond, session.control, session.admin, cron.schedule, cron.modify, subagent.spawn, subagent.cancel, subagent.output, subagent.spawn.operator, fs.see.private, fs.see.secrets, security.bypass.low, security.bypass.medium, security.bypass.high, plus every plugin-contributed security.bypass.<guard> (wildcard expansion)
trusted[]mediumchannel.respond, session.control, session.admin, cron.schedule, subagent.spawn, subagent.cancel, subagent.output, subagent.spawn.operator, fs.see.private, fs.see.secrets, security.bypass.low, security.bypass.medium
member[]lowchannel.respond, session.control, subagent.spawn, subagent.cancel, subagent.output, fs.see.private, security.bypass.low
guest[] (fallback)(none)(none)

User-declared permissions[] replaces the built-in list. "permissions": [] means no permissions, including no channel.respond.

The audience-leak defense (owner-in-public-channel) moved from the language default to operator config: scope roles.owner.match[] tightly. Default match is TUI-only, where a human is present. Configs that widen owner to a channel author re-open audience-leak for that author and should remove security.bypass.high (and the wildcard sentinel) from roles.owner.permissions[] for those origins.

guest carries no permissions by default, but it is grantable — an operator may add channel.respond to guest to let strangers drive masked turns. The hard fail-safe floor is therefore not the guest role but the undefined origin: a call with no resolvable actor holds nothing, regardless of what guest is granted. session.control is split out from channel.respond for exactly this reason — a respond-capable guest can talk but cannot abort other speakers' sessions (/stop gates on session.control, which stays member-and-up by default).

You don't have to hand-edit typeclaw.json#roles to grant a role. From the TUI or a 1:1 DM, an owner or trusted user can ask the agent to grant roles conversationally via the grant_role tool — see Runtime role grants below.

Core permissions

PermissionGranted by default toEffect
channel.respondowner/trusted/membergate on the channel router; without it, inbound messages are dropped before session creation
session.controlowner/trusted/membercontrol session lifecycle, e.g. /stop; split from channel.respond so a respond-only guest can't abort other speakers' sessions
session.adminowner/trustedoperate the agent from a channel: /reload (config + subsystems) and /restart (bounce the container); above session.control because both drop every in-flight session
cron.scheduleowner/trustedallow scheduling new cron jobs at runtime
cron.modifyownerallow editing existing cron jobs
subagent.spawnowner/trusted/memberallow spawning subagents
subagent.cancelowner/trusted/memberallow cancelling in-flight subagents
subagent.outputowner/trusted/memberallow reading subagent output
subagent.spawn.operatorowner/trustedallow spawning write-capable (operator) subagents
fs.see.privateowner/trusted/membersee the private working surface (workspace/, memory/, sessions/); without it those dirs are hidden from bash and the file tools
fs.see.secretsowner/trustedsee the credential files (.env, secrets.json); without it they are masked
security.bypass.lowowner/trusted/membertier-wide bypass for low-severity guards
security.bypass.mediumowner/trustedtier-wide bypass for medium-severity guards
security.bypass.highownertier-wide bypass for high-severity guards
security.bypass.<guard>variesper-guard bypass; see tier tables below

Plugin-contributed permissions live under the plugin's namespace (<plugin>.<verb>.<noun>). They're declared on definePlugin({ permissions: [...] }) and surface in roles.<name>.permissions[] once the plugin is loaded.

fs.see.private / fs.see.secrets are phrased as capabilities to see so the tower stays monotonic: a role without them has the matching paths hidden from both bash (bwrap masks) and the file tools (the privateSurfaceRead guard). A custom role can carry them explicitly, e.g. "contributor": { "match": ["slack:T0 author:U_C"], "permissions": ["channel.respond", "fs.see.private"] } sees the working surface but not the secret files. The agent folder's top-level public/ directory is never hidden by either grant — it is the one zone a guest can read and write. See the sandbox internals for the enforcement details.

Security guards — high tier

Audience-leak severity. Bypass sends data to a third-party audience outside the operator's control loop. owner bypasses high by default under the role-tower model; trusted and member do not. The owner-in-public-channel defense lives in roles.owner.match[] discipline (default: TUI-only).

GuardPer-guard permissionBlocks
outboundSecretsecurity.bypass.outboundSecretoutbound channel message containing a secret-shaped string
systemPromptLeaksecurity.bypass.systemPromptLeakoutbound message echoing the system prompt
gitRemoteTaintedsecurity.bypass.gitRemoteTaintedpush after a same-session git remote set-url

Security guards — medium tier

Silent-attack severity. Bypass produces attacker-favorable state in operator-reviewable surface without immediate audience-leak. owner and trusted bypass by default; member does not.

GuardPer-guard permissionBlocks
secretExfilBashsecurity.bypass.secretExfilBashbash env, bash printenv, etc.
secretExfilReadsecurity.bypass.secretExfilReadread .env, read secrets.json, etc.
ssrfsecurity.bypass.ssrfcurl http://169.254.169.254/... and other IMDS / SSRF targets
sessionSearchSecretssecurity.bypass.sessionSearchSecretssession_search query returning secret-shaped hits
gitExfilsecurity.bypass.gitExfilgit push to an unfamiliar remote (a clean operator-configured remote is not a leak; the retarget-and-push breach is gated by gitRemoteTainted at high)
rolePromotionsecurity.bypass.rolePromotionwrite/edit to typeclaw.json that widens role privileges or adds a privileged role
cronPromotionsecurity.bypass.cronPromotionwrite/edit to cron.json adding new jobs or changing scheduledByRole

Security guards — low tier

Reserved tier. No inhabitants today. Exists so member's bypass.low grant has a forward-compatible home when a future guard ships at low. owner, trusted, and member all carry bypass.low by default.

Runtime role grants

The agent ships a grant_role tool so an operator can edit typeclaw.json#roles by conversation instead of hand-editing the file. It does one of two things per call:

  • Match grant — assigns an author/scope to a role, e.g. grant member the match rule slack:T0123 author:U_X. Takes effect immediately (the live permission table is hot-reloaded) and the change is committed to typeclaw.json.
  • Permission grant — adds a capability to a role, e.g. grant guest channel.respond. Written to typeclaw.json but restart-required: it lands on disk and takes effect on the next typeclaw restart.

Gates, all enforced at the tool boundary:

  • Origin — callable only from the TUI or a 1:1 DM. A group/open channel turn is refused, because it mixes in other participants' messages (the confused-deputy / prompt-injection surface that could trick a trusted turn into rewriting the access-control table).
  • Caller role — the caller must resolve to owner or trusted. member and guest cannot grant.
  • Tier ceiling — a caller cannot grant a role above its own. A trusted caller can grant trusted/member/guest; only owner can grant owner.
  • Grant only what you hold — for permission grants, the caller can only confer a capability it itself possesses.
  • No security.bypass.* — bypass permissions disable guards rather than enable a feature; grant_role refuses them. They remain a deliberate hand-edit gated by the rolePromotion guard.

This is the conversational counterpart to the CLI typeclaw role claim flow (/reference/cli#roles): role claim pairs the current author via a one-time code, while grant_role lets an already-trusted operator grant any author or capability mid-conversation.

Acceptance logic

tool.before accepts EITHER the tier permission (security.bypass.low|medium|high) OR the per-guard permission. The two axes work forever; adding a per-guard permission to a role doesn't remove the role's tier-wide grants.

The bundled security plugin reads each guard's exported GUARD_*_SEVERITY constant at boot. Adding a new guard without an exported severity is a TypeScript error, not a silent permissive fallback.

On this page