Skip to main content

Authorization model

The relationship graph that authorizes access to resources. Authority for this page is config/auth/openfga/model.fga; the deployable payload config/auth/openfga/authorization-model.json is generated from it.

Synopsis

Identity types are PascalCase; governance types are lowercase. Object references are literal, so a tuple reads resource:<uuid>#reader@User:<id>.

Fields

Workspace

role

A role is a bag of independent bits. Each enabled bit must be written for both User:* and Agent:*; a bit written only for User:* denies agent assignees.

role_assignment

Attach each assignment to exactly one object; attaching one assignment to several objects amplifies its bits across all of them.

project

resource

Agents, skills, MCP servers and registered clients are all resource:<uuid>. Which kind a resource is lives in the database, not the graph.

Values

Permission bits

can_read, can_write and can_manage are independent. None implies another. A role granting only can_write yields can_write: true with can_read: false and can_manage: false.

Verb to bit — permission service

Applied by require_permission and the DI-resolved permission service. A verb outside this map is denied and logged at warning level.

Verb to bit — relationship explorer

Applied by the /v1/access-control check endpoint. Note the absence of update. A value that is neither a mapped verb nor one of the three bits returns allowed: false without querying the graph.

Relation aliases accepted on write

The explorer write endpoint maps a submitted relation onto a resource grant. Anything else is rejected. A subject_set (group) body is rejected; bundled access goes through role and role_assignment.

Namespaces accepted by the explorer

The namespace selects a repository for that check only. The tuple written or checked is always against resource:<object>.

Subject forms accepted on write

Seeded tuples

On workspace creation: On governed resource creation: The root project id is the workspace id suffixed with -root. Both writers are idempotent; an “already exists” response is treated as success.

Enforcement

Invariants the graph cannot express, enforced by the writer instead: one workspace and one parent per project, one project per resource, same-workspace edges, an acyclic project graph, one object per role_assignment, and resource kind resolved from the database.

Defaults and overrides

ACCESS_CONTROL_BACKEND accepts disabled, keto or openfga. keto.enabled and openfga.enabled are mutually exclusive; the chart fails rendering if both are set. docker-compose.dev.yaml and the Helm chart both set AUTO_BOOTSTRAP and AUTO_APPLY_MODEL to true. Model paths differ by deployment: With AUTO_APPLY_MODEL enabled the file wins and the returned model id is used for the process lifetime. With it disabled, ACCESS_CONTROL_OPENFGA_AUTHORIZATION_MODEL_ID is used and the file is ignored. Bootstrap compares candidate models by normalized content ignoring ids, so an unchanged model is reused rather than rewritten. When several processes race a store create, all converge on the earliest store with that name.

Example

Grant a write-only role bundle on a project, and confirm the bits do not roll up:

See also

  • Policy rule syntax — the other authorization surface, which governs tool invocation.
  • Limits — timeouts and pagination on the graph client.
  • Errors — the 403 and 503 responses these checks produce.
  • The AgentArea model — the reasoning behind the type set.