RBAC
Seven built-in roles, 140-odd permissions, two extra gates, and how to build a custom role that is actually minimal.
Permissions are resource:action. The console uses them to decide what to
render; the API uses them to decide what to allow. Only the second one
is authoritative.
The complete list, with which roles hold what, is in Permissions and roles — generated from the source, so it cannot drift.
#The built-in roles
| Role | For |
|---|---|
OWNER |
Full control, including organization deletion and billing |
ADMIN |
Workspaces, people, policies, integrations |
SECURITY_ADMIN |
Security, risk, credentials, incidents, emergency controls |
DEVELOPER |
Build and operate agents, tools, policies, delegations |
APPROVER |
Review and decide approval requests |
AUDITOR |
Read-only access to audit, security and compliance evidence |
VIEWER |
Read-only operational data |
Roles are a starting point, not a ceiling. Create custom roles when a team needs a different shape.
#Two extra gates
High impact cannot come from a wildcard
Nine permissions must appear on a role explicitly:
agent:revoke · agent:decommission · credential:revoke ·
policy:activate · policy:rollback · security:emergency_action ·
security:break_glass · organization:delete · environment:pause
A role granted "everything in the agent resource" does not get agent:revoke.
The reasoning: these are irreversible or have a large blast radius, and a
wildcard is almost always someone being approximate rather than someone deciding
to grant terminal power.
Step-up is required at the moment of use
Thirteen permissions additionally require a fresh re-authentication, regardless of how old the session is:
agent:revoke · agent:decommission · credential:revoke ·
credential:rotate · policy:activate · policy:rollback ·
security:emergency_action · security:break_glass · sso:update ·
organization:delete · audit:configure_retention · environment:pause
Holding the permission is not enough. An unattended, still-valid browser session cannot revoke a production agent. → Step-up
Note policy:activate and policy:rollback are on both lists. Activating a
policy version moves the authorization boundary for every agent in scope, which
puts it in the same category as revoking an identity.
#Separation of duties, by default
Three defaults are worth knowing because they are deliberate and people sometimes "fix" them:
SECURITY_ADMIN cannot approve. It does not hold approval:approve. The
person who wrote the rule requiring approval should not be the person satisfying
it.
DEVELOPER cannot export audit. It holds audit:read but not
audit:export. Reading the trail and removing a copy of it are different
privileges.
DEVELOPER cannot record trust evidence. trust:manage belongs to
SECURITY_ADMIN. An agent's author should not be the one attesting the agent is
safe.
Grant the overlap explicitly if your organization genuinely wants it, having decided to.
#Build a minimal custom role
Start from what the task requires, not from a role you trim.
An on-call responder — can contain, cannot change the rules:
json{
"key": "on-call-responder",
"name": "On-call responder",
"permissions": [
"agent:read", "agent:pause", "agent:resume",
"action:read", "security:read",
"incident:read", "incident:manage",
"tool:read", "tool:disable",
"audit:read"
]
}
No agent:revoke — containment should be reversible at 3am. No
policy:activate — policy changes belong in a change process, not in an
incident.
A CI policy gate:
json{
"key": "ci-policy-gate",
"name": "CI policy gate",
"permissions": ["policy:read", "policy:simulate", "agent:read", "tool:read"]
}
Enough to run veefore test. Not enough to change anything.
An audit exporter for a SIEM:
json{
"key": "siem-exporter",
"name": "SIEM export",
"permissions": ["audit:read", "audit:export"]
}
#Scope a grant to a workspace
A role grant can be limited to one workspace, so the payments team's developers operate payments agents and cannot touch the logistics team's.
bashcurl -X POST $CP/v1/members/mbr_01ABC/roles \
-H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{ "role_id": "role_01JAV…", "workspace_id": "wsp_01JAV…" }'
Scope grants whenever the team only needs one workspace. It is the cheapest reduction in blast radius available.
#API keys
An API key carries a role's permissions and no more.
Never an `OWNER` key in CI
A CI secret lives in your CI provider's storage, is readable by every build step,
and ends up in logs if anyone echoes the environment. OWNER includes
organization:delete.
Create a custom role with exactly what the key needs. It takes two minutes. → API keys
Step-up permissions are unavailable to API keys, so a key cannot activate a policy or revoke an agent regardless of its role.
#Policy can see the principal
json{ "kind": "condition", "id": "c-principal", "field": "principal.type", "operator": "eq", "value": "API_KEY" }
USER, AGENT, SYSTEM or API_KEY. Useful for rules that should apply to
automation but not to people, or the reverse.
#Break-glass
security:break_glass grants emergency elevated access. High impact, step-up
gated, CRITICAL audit events, every use listed at
GET /v1/security/break-glass.
Grant it to as few people as possible and review every use
Its whole purpose is to be the thing you notice. Held by half the team, it is not an emergency mechanism — it is a standing privilege with a dramatic name.
Review the list monthly. Every entry should correspond to an incident somebody remembers.
#Review it quarterly
Three questions, which cover most of what goes wrong:
Who holds security:emergency_action? It should be a small, current set.
Who holds policy:activate? This is the authorization boundary. It should
be a deliberate list.
Who holds audit:export? Evidence leaving the system should be a short
list.
Then: does anyone hold OWNER who does not need to? Is any grant
organization-wide that could be workspace-scoped? Does any role still exist for
a team that no longer does?
SCIM deprovisioning handles leavers automatically if you have it configured. → SSO and SCIM