ZeroRoot Docs
Security architecture

Authorization

How Gibson decides what an agent, tool, or plugin may do. Every component has a human owner. Read, write, and execute are separate grants. A deny at tenant, team, or user scope always wins.

Authorization is an OpenFGA model, the Google Zanzibar relationship model. The model file and its generated registry are the source of truth. The registry is generated. Nobody edits it by hand.

The short version: an agent cannot do anything nobody granted it, and every agent traces back to a named person.

Every component has a human owner

Agents, tools, and plugins are not users with a flag set. They are their own principal types, and each one declares an owner:

type agent_principal
  relations
    define owner: [user]
    define belongs_to: [tenant]
    define cannot_invoke: [component]

tool_principal and plugin_principal have the same shape.

What that gets you: the model records which human is answerable for each agent as a relation. It is not a convention that someone has to keep up. You cannot enroll an agent that belongs to nobody. The answer survives when the person changes teams.

Read, write, and execute are separate

Every component carries three effective relations:

RelationWhat it allows
can_readSee the target component's state, recent runs, and metadata
can_configureChange the target's settings
can_executeInvoke it — run the agent, call the tool

A grant of one is not a grant of another. An agent with can_read on a tool cannot run it.

A deny at any scope wins

Three scopes can switch a component off. The model subtracts them from the grant. It does not race them:

can_execute: (direct_execute and in_tenant_catalog) but not any_execute_deny

Here any_execute_deny is the union of the tenant-, team-, and user-scoped disables. The same shape applies to read and configure.

What that gets you: one kill switch that no grant elsewhere can out-vote. If you disable a component at tenant scope, no team grant and no user grant can enable it again. One write turns a component off. You do not need to find and undo every grant that turned it on.

in_tenant_catalog is the second half. A component is reachable only if your tenant has enabled it. A component published to the platform is not automatically available inside your tenant.

An agent that acts as itself needs two keys

When the caller is a component and not a person, the model requires both the owner-side grant and a per-component grant:

can_read_as_component:    can_read    and component_read_enabled
can_write_as_component:   can_configure and component_write_enabled
can_execute_as_component: can_execute and component_execute_enabled

component_*_enabled is absent by default, and absent means denied. The owner approves it at install time.

What that gets you: a grant to a person for a tool does not silently grant that tool to every agent the person owns. The agent's own authority is a separate and narrower decision. The owner makes it once, and it is visible as its own tuple.

Block a specific pairing

Two relations handle the cases a grant cannot express:

  • cannot_invoke on a principal blocks named components outright.
  • can_be_invoked_by on a component controls which agents may call it.

So you can allow an agent broad execute rights and still fence off one tool. You do not need to rebuild the grant set.

Where a person manages this

A person manages grants in the dashboard's grants table, over the tenant grants API. The operator's walkthrough covers roles, the permissions tab, and the deploy wizard. It is on Roles & permissions. This page is the model underneath it.

What a human does and does not do

A person does: own components, write and revoke grants, enable or disable a component at tenant, team, or user scope, and set the mission constraints.

A person does not: approve individual actions while a mission runs. That is a deliberate decision, recorded as an ADR. See Runtime. A person declares control before the agent starts. The agent does not request control while it works.

What to check

  1. Every principal type declares owner: [user].
  2. can_read, can_configure, and can_execute are independent.
  3. The model subtracts deny from the grant, so a disable at any scope wins.
  4. component_*_enabled is absent by default, and absent denies.
  5. The authz registry is generated, and CI fails if a hand edit drifts from it.

On this page