Roles and permissions
RunR authorizes every API request against a single permission catalog. Users get permissions through roles: their own role in the organization, the roles of the groups they belong to, and project access — a role on one project only. API tokens carry a subset of their owner's permissions. Platform administrators operate the installation itself.
The same model runs in both editions:
| Single-tenant | Multi-tenant | |
|---|---|---|
| Organization | the installation | a tenant |
| Member's own role | stored with RunR's role assignments | the tenant membership (tenant_users.role) |
| Custom roles, groups, project access, SSO mappings | installation database | the tenant's schema |
| Billing role and permissions | — | available |
Platform administrator (users.is_admin) |
every permission | every permission, in every tenant and in the central context |
The catalog and the built-in roles are defined in one file: api/config/permissions.php. What each API route requires is listed in api/config/route_permissions.php.
Built-in roles#
Built-in roles are read-only and exist in every organization.
| Role | Key | For | Permissions |
|---|---|---|---|
| Owner | owner |
the people responsible for the organization | every permission of the edition (*), plus ownership |
| Admin | admin |
administrators | every permission of the edition (*) |
| Developer | developer |
building, deploying and operating projects | projects.view, projects.create, projects.update, projects.delete, projects.deploy, code.edit, env.view, env.edit, domains.manage, connections.manage, git.pull, git.push, git.manage, previews.manage, shell.exec, logs.view, metrics.view, traces.view, replay.manage, uptime.manage, workflows.view, workflows.run, workflows.manage, events.send, sandboxes.view, sandboxes.create, sandboxes.exec, security.view, regions.view, runtimes.use_custom, images.view, images.upload, images.delete, models.view, models.deploy, bindings.view, bindings.manage, mcp.servers.manage, mcp.servers.call, members.view, tokens.manage, tickets.view, tickets.create |
| Viewer | viewer |
read-only users | projects.view, git.pull, logs.view, metrics.view, traces.view, workflows.view, sandboxes.view, security.view, regions.view, images.view, models.view, bindings.view, mcp.servers.call, members.view, tokens.manage, tickets.view, tickets.create |
| Billing | billing |
finance (multi-tenant only) | billing.view, billing.manage, regions.view, bindings.view, members.view, tokens.manage, tickets.view, tickets.create |
"Every permission of the edition": a single-tenant install has no tenants.residency.manage, billing.view and billing.manage; a multi-tenant install has no git_providers.manage, runtimes.manage, signup.manage and tickets.manage (the platform administrators do that work there). Roles hold the permissions they list plus the ones those include (see the Includes column of the catalog).
Owner and Admin differ only in ownership:
- only owners (and platform administrators) grant or revoke the Owner role;
- the last owner of an organization can be neither demoted nor removed;
- in multi-tenant installs only tenant owners point a new region at their own monitoring endpoints (Prometheus / Loki).
Permission catalog#
Permissions with scope project can also be granted on single projects (see Project access). The Includes column lists the permissions that always come with a permission (added when a role or a token is saved and when it is resolved); they chain: for example env.edit includes env.view, which includes projects.view. A scope marked single-tenant or multi-tenant exists in that edition only.
Projects#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
projects.view |
project | — | see projects, code, versions, deployments and gateway routes |
projects.create |
organization | — | create projects, clone them, load demo projects |
projects.update |
project | projects.view |
change description, region, resources, scaling and triggers |
projects.delete |
project | projects.view |
delete projects with their workloads |
projects.deploy |
project | projects.view |
deploy versions, stop and delete deployments |
projects.manage_access |
project | projects.view |
give users and groups a role on the project |
code.edit |
project | projects.view |
save code and files (creates versions) |
Git & previews#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
git.pull |
project | projects.view |
clone and fetch the project's git remote, see the Git tab, deploy history and previews |
git.push |
project | git.pull |
push to the project's git remote (a push to the deploy branch creates a version, and deploys it with auto-deploy on); redeploy the deploy branch head |
git.manage |
project | git.pull |
deploy branch, root directory, auto-deploy and force pushes; connect and disconnect a GitHub, GitLab or Forgejo/Gitea repository |
previews.manage |
project | git.pull |
preview settings (on/off, limit, lifetime, overrides, forks); redeploy and delete previews |
git_providers.manage |
organization (single-tenant) | — | Settings → Git providers: the GitHub App, GitLab and Forgejo/Gitea instances; in multi-tenant installs platform administrators configure the providers every tenant shares |
See Git deploy.
Configuration#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
env.view |
project | projects.view |
see environment variables (others get env_vars: null, env_vars_hidden: true) |
env.edit |
project | env.view |
add, change and remove environment variables |
secrets.view_values |
project | env.view |
reveal the values of secret variables |
domains.manage |
project | projects.view |
add, verify and remove custom domains and their TLS |
connections.manage |
project | projects.view |
private connections to managed databases |
Operations#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
shell.exec |
project | projects.view |
open a shell in a running deployment |
logs.view |
project | projects.view |
read deployment logs |
metrics.view |
project | projects.view |
read live and historical metrics |
traces.view |
project | projects.view |
search and read the project's traces (Observability); the logs of a trace also need logs.view |
replay.manage |
project | projects.view |
see, replay and delete captured failed requests (request replay) |
uptime.manage |
project | metrics.view |
create, change, run and delete uptime checks, update incidents; reading them is metrics.view |
alerts.manage |
organization | — | alert channels (with send test), alert rules, alert events and the delivery log with retry |
status_page.manage |
organization | — | the organization's public status page |
Workflows#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
workflows.view |
project | projects.view |
see workflow definitions, runs, steps and usage; organization-wide also the tenant's events and usage |
workflows.run |
project | workflows.view |
start, cancel, retry and replay runs |
workflows.manage |
project | workflows.view |
sync / declare definitions, pause and resume workflows, set the endpoint override, rotate and reveal the signing key |
events.send |
organization | — | send events (POST /api/events) |
Sandboxes#
Sandbox API: isolated sandboxes for untrusted code.
| Permission | Scope | Includes | Allows |
|---|---|---|---|
sandboxes.view |
organization | — | list and read sandboxes, their activity log, regions, warm pools, the organization's quota, metrics and usage |
sandboxes.create |
organization | sandboxes.view |
create, delete and extend sandboxes, change their network |
sandboxes.exec |
organization | sandboxes.view |
run commands and code, the terminal, files, background processes, keepalive, pause / resume |
sandboxes.manage |
organization | sandboxes.view |
region sandbox settings (with the region's ownership) and warm pools; platform administrators: quotas of every organization and the Prometheus metrics |
Security#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
security.view |
project | projects.view |
see SBOMs, vulnerability scans, signatures and policy overrides of the project's images; download SBOMs; start a scan |
security.override_policy |
project | security.view, projects.deploy |
deploy a version the supply-chain policy gate blocks, with a recorded reason |
Infrastructure#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
regions.view |
organization | — | see regions, their components and status |
regions.manage |
organization | regions.view |
create, configure, upgrade, migrate and delete regions; Knative, Redis, workers, capacity overcommit factors, function TLS, demo projects, kubeconfig download |
sandbox.manage |
organization | regions.view |
install and configure the region's sandbox runtimes (gVisor, Kata Containers) and WebAssembly support (WebAssembly) |
registry.manage |
organization | regions.view |
set up and remove region registries |
regions.data_services.manage |
organization | regions.view |
install, configure and remove the data services of a region: KV Redis, object store, CloudNativePG (Bindings) |
regions.supply_chain.manage |
organization | regions.view |
a region's SBOM / scan settings, vulnerability DB, policy gate, signing key and admission policy; the organization's policy gate |
regions.observability.manage |
organization | regions.view |
a region's tracing backend: mode (managed, reuse, external, off), install and uninstall, export headers |
runtimes.manage |
organization (single-tenant) | — | register and change platform runtimes, enable built-in ones, switch custom runtimes on; in multi-tenant installs platform administrators manage the runtimes every tenant shares |
runtimes.use_custom |
organization | — | register the organization's own runtimes and run projects on them, while the platform allows custom runtimes (Runtimes) |
regions.gpu.manage |
organization | regions.view |
install and remove GPU support (NVIDIA device plugin / GPU Operator), GPU settings (time-slicing, RuntimeClass, gVisor nvproxy) and the model volume of regions (AI); platform regions of a multi-tenant install stay with the platform administrators |
Images#
Container images and WebAssembly modules uploaded into region registries (Images). Running an uploaded image in a CaaS project, or naming one in a runtime version, needs nothing beyond creating / changing the project or the runtime.
| Permission | Scope | Includes | Allows |
|---|---|---|---|
images.view |
organization | — | see the organization's uploaded images, their scans and the storage quota; pick them in the CaaS form and the runtime editor |
images.upload |
organization | images.view |
upload docker save / OCI image-layout tars and .wasm modules into a region registry, resume and abort uploads |
images.delete |
organization | images.view |
delete uploaded images and their manifests in the region registry |
Models#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
models.view |
organization | — | browse the Hugging Face Hub, see models, offline copies, model services, their logs and bindings, use the playground (AI) |
models.deploy |
organization | models.view |
deploy, change, stop and delete model services; mirror models into regions and upload model files; bind model services to projects (with projects.update on the project) |
models.manage |
organization | models.view |
the organization's Hugging Face token; import models from the model volume or an OCI artifact; delete models and offline copies |
Data#
Bindings are resources of the organization (Bindings). Attaching, re-prefixing and detaching on a project needs env.edit on that project as well as bindings.manage.
| Permission | Scope | Includes | Allows |
|---|---|---|---|
bindings.view |
organization | — | see KV, object storage, Postgres and external bindings, their attachments, health and usage |
bindings.manage |
organization | bindings.view |
create, change, attach, detach, rotate, delete and restore bindings |
bindings.view_credentials |
organization | bindings.view |
reveal the connection credentials of a binding (audited) |
AI agents (MCP)#
AI agents. The tools of the RunR MCP server use the permissions above — an agent can do what its token can do; approving an OAuth client needs tokens.manage.
| Permission | Scope | Includes | Allows |
|---|---|---|---|
mcp.servers.manage |
project | projects.view |
serve a project as a hosted MCP server, change its access mode, path and audit flag, rotate its gateway key, stop serving it |
mcp.servers.call |
project | projects.view |
call the project's hosted MCP server through the gateway (token and OAuth access modes) and use the inspector |
Organization#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
members.view |
organization | — | see members, groups and roles |
members.invite |
organization | members.view |
add users and send invitations (with a role the inviter can grant) |
members.update |
organization | members.view |
edit member profiles, reset two-factor authentication |
members.remove |
organization | members.view |
remove members |
roles.manage |
organization | members.view |
create custom roles, assign roles to members and groups, set the default role |
groups.manage |
organization | members.view |
create groups and manage their members |
tokens.manage |
organization | — | create and revoke own API tokens |
settings.manage |
organization | — | base domain, registration, demo data; single-tenant: the audit log retention; multi-tenant: complete the onboarding wizard of a self-service tenant |
branding.manage |
organization | — | the brand (White-label) — single-tenant the install's, multi-tenant the tenant's own (the platform brand is the platform administrators') |
signup.manage |
organization (single-tenant) | — | self-service signup settings (single-tenant; multi-tenant: platform administrators) |
sso.manage |
organization | — | sign-in providers and SSO group mapping |
audit.view |
organization | — | see the audit log |
audit.export |
organization | audit.view |
export the audit log |
tenants.residency.manage |
organization (multi-tenant) | — | data residency of the organization: allowed regions and data locations, default region (Data residency) |
Billing (multi-tenant)#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
billing.view |
organization (multi-tenant) | — | see the tenant's charges, credit and purchases (Settings → Charges); on a platform administrator's API token: read the billing API |
billing.manage |
organization (multi-tenant) | billing.view |
the link to the operator's store in Settings → Subscription; on a platform administrator's API token: write through the billing API — plan changes, purchases, charges, event redelivery (Billing) |
Support#
| Permission | Scope | Includes | Allows |
|---|---|---|---|
tickets.view |
organization | — | see own support tickets |
tickets.create |
organization | tickets.view |
open support tickets and reply |
tickets.manage |
organization (single-tenant) | tickets.view |
answer every ticket (single-tenant; in multi-tenant installs the platform administrators answer tickets) |
Permissions are also Laravel Gate abilities: $user->can('projects.deploy', $project), Gate::authorize('audit.export'), or ->can('audit.view') on a route.
Custom roles#
Users with roles.manage create roles of their own in Settings → Roles: a name, a description and a selection in the permission matrix. Rules:
- a custom role holds only permissions its editor holds — nobody hands out more than they have;
- implied permissions are added automatically;
- deleting a custom role moves its members to the default role and removes the group roles, project grants and SSO role mappings that used it.
Default role#
New members get the organization's default role: users added on the Users page without a role, self-registration, invitations without a role and users created by single sign-on. It is set on the Roles page (PUT /api/roles/default); the installation default is developer (RBAC_DEFAULT_ROLE, with the Helm chart set through extraEnv). The default role is never Owner.
Groups#
Groups (Settings → Groups) bundle members. A group can have an organization-wide role — every member gets it in addition to their own role — and access to single projects. Effective permissions are the union of all roles. Adding someone to a group requires being able to grant the group's roles.
Memberships have a source: manual (added in the console) or sso (kept in sync by SSO group mapping). Adding a member manually makes an SSO membership permanent.
Project access#
The Access tab of a project gives a user or a group a role on that project only. Only the role's project-scoped permissions apply there. Example: a member with the Viewer role organization-wide and the Developer role on project checkout can deploy checkout and only view everything else.
- Members without organization-wide
projects.viewsee only the projects they have access to. - Whoever creates a project without seeing every project gets the Admin role on it.
projects.manage_accessallows granting roles on the project — roles whose project permissions the granter holds on that project.- The Owner role is not used for project access.
API tokens#
Personal API tokens (Settings → API Tokens) carry either full access (*) or a selection of permissions:
- a token can only be created with permissions its owner holds (organization-wide or on some project);
- at every request the token is limited to what its owner may do at that moment — changing or removing a member's role limits their tokens immediately;
- tokens optionally expire (
expires_at); - a token created inside a tenant works in that tenant only;
- a token of a platform administrator with a selection of permissions is limited to them: it reaches the routes marked
@platform:<permission>when it carries that permission (the billing API — a token with onlybilling.vieworbilling.manageis the way to connect a billing system), never the routes marked@platform; - tokens RunR issues to MCP clients through OAuth (AI agents) are API tokens as well — named
<client> (MCP), with the permissions and lifetime approved on the consent page, revocable here like any other.
GET /api/tokens returns the permissions a new token may carry (available_abilities).
SSO group mapping#
Sign-in providers (Settings → Auth Providers, OIDC and SAML) can manage groups and roles:
- Groups claim / attribute — the OIDC claim (from the userinfo response) or SAML attribute that carries the user's groups. When empty, RunR reads
groups, the Entra ID group claim (http://schemas.microsoft.com/ws/2008/06/identity/claims/groups) andmemberOf. - Role for new users — the role users created just in time by this provider get; empty = the organization's default role.
- Group mappings — external group → group and/or role, for one provider or every provider. Matching is case-insensitive.
On every SSO sign-in:
- Every mapped group follows the IdP: the member joins (source
sso) when one of their external groups maps to it and leaves when none does. Manual memberships stay. - When at least one mapping that applies to the provider (its own or one for every provider) sets a role, the IdP manages the member's organization-wide role: the matching role with the most permissions, else the provider's role for new users, else the organization's default role. Owners keep their role.
- Just-in-time provisioning (
auto_register): a new account becomes a member with the role for new users (or a mapped role). In multi-tenant installs the account is a platform account that joins the tenant; an existing account with the same e-mail joins the tenant instead of being created again.
Platform administrators#
users.is_admin marks the operators of a multi-tenant installation: tenants, quotas, cost configuration, charges and plans, platform regions, impersonation and support across tenants. They pass every permission check (Gate::before) in every tenant, unless they use an API token with a selection of permissions. Routes for them only are marked @platform in the route map; routes marked @platform:<permission> (the billing API) also accept their tokens that carry the permission. Other users have no permissions in the central context (no active tenant).
Enforcement#
App\Http\Middleware\AuthorizeApiRoute(api middleware group) checks every authenticated API route againstconfig/route_permissions.php, after authentication, tenant identification and route model binding. A route with{project}is checked for that project. A route behindauth:sanctumwithout a rule is refused (403).- Policies refine checks inside controllers:
ProjectPolicy,GatewayPolicy(gateways belong to their creator; routing a project through one needsprojects.updateon the project),RolePolicy,GroupPolicy. Updating a project needsenv.editfor changed environment variables andprojects.updatefor every other field. Tests\Feature\Rbac\RouteAuthorizationTestfails when an API route has no rule, when a rule names an unknown permission or a route that no longer exists, and when a@policyroute's controller does not authorize.
For contributors: adding a route or a permission#
Every API route behind auth:sanctum needs a rule — the API refuses unmapped routes (403) and php artisan test tests/Feature/Rbac/RouteAuthorizationTest.php fails on them, and on rules whose route no longer exists. Add the route, then one line to config/route_permissions.php:
'GET audit-events' => 'audit.view',
'POST projects/{project}/rollback' => 'projects.deploy', // checked for {project}
'GET reports' => 'projects.view:any', // org-wide or on some project
'GET things' => 'regions.manage|registry.manage', // any of them
'POST things' => ['regions.manage', 'registry.manage'], // all of them
'GET status' => '@authenticated', // any signed-in user
'GET admin/plans' => '@platform', // platform administrators only
'GET billing/charges' => '@platform:billing.view', // platform administrators; a token must carry billing.view
'PATCH gateways/{gateway}' => '@policy', // the controller authorizes (Policy)
or put ->can('audit.view') on the route. Finer checks inside a controller use the same names: Gate::authorize('env.edit', $project), $request->user()->can('audit.export').
A new permission:
- one entry in
config/permissions.php(group,scope—projectwhen it can be granted on single projects —,implies,editionwhen it exists in one edition only); - the built-in roles that should hold it (Owner and Admin hold everything);
- a label and a hint in
vue/src/locales/en/rbac.jsandvue/src/locales/tr/rbac.js(rbac.perm.<name with _>,rbac.permHint.<name with _>) —PermissionCatalogTestchecks both; - the row in this page's catalog, and the mock catalog of the UI tests (
vue/e2e/fixtures/data.ts).
In the console, usePermissionsStore().can('name', projectId) hides what the user cannot do; the API decides either way.
CLI#
runr members, runr roles, runr groups, runr access and runr tokens administer members, roles (including runr roles permissions, the catalog, and runr roles default), groups, project access and API tokens with the permissions listed below (CLI: access control).
API#
| Endpoint | Requires | |
|---|---|---|
GET /api/auth/permissions |
signed in | effective permissions: organization-wide, per project, token limit |
GET /api/permissions |
signed in | the catalog of this edition |
GET /api/roles |
signed in | built-in and custom roles, default role, which ones the caller can grant |
GET /api/roles/{role} |
signed in | one role with its permissions |
POST /api/roles, PUT/DELETE /api/roles/{role} |
roles.manage |
custom roles |
PUT /api/roles/default |
roles.manage |
default role |
GET /api/members |
members.view |
members with role and groups |
PUT /api/members/{user}/role |
roles.manage |
a member's own role |
GET /api/groups, GET /api/groups/{group} |
members.view |
groups |
POST /api/groups, PUT/DELETE /api/groups/{group}, POST /api/groups/{group}/members, DELETE /api/groups/{group}/members/{user} |
groups.manage (+ roles.manage for the group role) |
groups |
GET/POST /api/projects/{project}/access, DELETE …/access/{assignment} |
projects.view / projects.manage_access |
project access |
GET/POST /api/sso/group-mappings, PUT/DELETE /api/sso/group-mappings/{mapping} |
sso.manage |
SSO group mapping |
GET/POST /api/tokens, DELETE /api/tokens/{token} |
tokens.manage |
own API tokens |
A refused request answers 403 with the permission it needs:
{ "message": "You do not have permission to do this (requires projects.deploy).", "permissions": ["projects.deploy"] }
Upgrade from 0.4.x#
The upgrade migrations give every existing user and token what the 0.4.x role or scope stood for. 0.4.x did not enforce two of them: a tenant viewer could change and deploy projects like a member, and API token scopes were stored but not checked. 0.5.0 enforces both — give a viewer who works on projects the Member or Developer role, and a scoped token that has to deploy the scope that allows it (upgrade.md).
| Before | After |
|---|---|
Single-tenant: the administrator account (ADMIN_USERNAME, else the oldest user) |
Owner |
| Single-tenant: every other user (all users could do everything) | Admin |
Multi-tenant: tenant owner |
Owner |
Multi-tenant: tenant admin |
Admin |
Multi-tenant: tenant member |
the custom role Member (key member), created in every tenant that has members or open invitations with that role — exactly what members could do: the Developer permissions without models.deploy, mcp.servers.manage and mcp.servers.call, plus settings.manage and sso.manage. A tenant schema the upgrade has not migrated yet resolves member as Developer. Move members to Developer to drop the settings and SSO permissions. |
Multi-tenant: tenant viewer |
Viewer (read-only — enforced from 0.5.0 on) |
users.is_admin |
platform administrator (unchanged) |
| Users created after the upgrade | the organization's default role (RBAC_DEFAULT_ROLE, default developer) |
| Users the previous version created while the upgrade rolled out (single-tenant) | the default role, from the Helm bootstrap job (php artisan rbac:assign-missing) |
| API tokens created without any scope | no permissions (only the routes that need none: own account, notifications); create a new token |
API tokens with * |
unchanged |
| API tokens with 0.4.x scopes | translated: projects:read → projects.view, logs.view, metrics.view, git.pull; projects:write → projects.view, projects.create, projects.update, projects.delete, code.edit, env.view, env.edit, domains.manage, git.pull, git.push; deployments:read → projects.view, logs.view, metrics.view; deployments:write → projects.view, projects.deploy; settings:read → regions.view; settings:write → regions.view, regions.manage, settings.manage |
Clients that still send 0.4.x scope names when creating a token get the same translation. Migrations: 2026_10_10_000001_create_rbac_tables, 2026_10_10_000002_assign_roles_to_existing_users, 2026_10_10_000003_translate_legacy_token_abilities, Tenancy 2026_10_10_100001_widen_tenant_user_role, AuthProvider 2026_10_10_200001_add_sso_group_mapping, tenant migrations 0040_create_rbac_tables, 0041_create_legacy_member_role, 0042_add_sso_group_mapping. The Helm pre-upgrade job runs all of them (including tenancy:migrate) before the new version serves requests.