Authorization in Arthur: How Access Control Works
The Big Picture
Arthur's authorization system answers one question over and over: "Can this user do this action on this resource?"
The system is built on three concepts that compose together:
- Permissions — atomic capabilities ("can read a model", "can delete a workspace")
- Roles — named collections of permissions ("Project Reader", "Org Admin")
- Role Bindings — assignments that say "User X has Role Y on Resource Z"
Every API call Arthur receives is evaluated against this model before any data is returned or changed.
┌────────────┐ holds ┌──────────────┐ scoped to ┌────────────┐
│ User or │──────────────▶│ Role Binding │────────────────▶│ Resource │
│ Group │ └──────┬───────┘ │(org/WS/proj│
└────────────┘ │ └────────────┘
│ grants
▼
┌─────────┐ contains ┌────────────────┐
│ Role │───────────────▶│ Permissions │
└─────────┘ │(read, create, │
│ delete, etc.) │
└────────────────┘
The Resource Hierarchy
Arthur organizes resources in a strict hierarchy. Every resource in the system lives at a specific level, and access flows top-down.
Platform (Arthur-managed, global)
└── Organization ◀── role bindings allowed
├── Workspace ◀── role bindings allowed
│ ├── Project ◀── role bindings allowed
│ │ ├── Model
│ │ │ └── Alert Rule
│ │ ├── Connector
│ │ ├── Dataset
│ │ └── Available Dataset
│ │
│ ├── Engine (Data Plane) ◀── role bindings allowed
│ │ └── Data Plane Association
│ ├── Webhook
│ ├── Agent
│ ├── Custom Aggregation
│ └── Custom Aggregation Test
│
├── Policy
│ ├── Policy Alert Rule
│ └── Policy Attestation Rule
├── User
└── Group
The four levels where role bindings can be attached are Organization, Workspace, Project, and Engine (Data Plane). Everything else — models, connectors, datasets, webhooks, agents, policies, alert rules — inherits access from its parent. There is no per-model or per-connector access control; it all flows from the project.
Level-by-Level Breakdown
Organization — The root of a customer's account. Every user, group, workspace, policy, and custom role belongs to exactly one org. Role bindings here affect org-wide configuration.
Workspace — A logical environment within the org (e.g., "Production", "Staging", "R&D"). Workspaces own projects, engines, webhooks, agents, and custom aggregations. Teams or business units typically get their own workspace.
Project — The primary unit for ML work. A project typically maps to a model use case or team initiative (e.g., "Fraud Detection v2"). Projects own models, connectors, and datasets.
Engine (Data Plane) — The compute engine that runs jobs. Engines are workspace-owned and have their own role level primarily for the service account that powers the engine process itself.
Leaf resources — Everything below the four bindable levels. Access is entirely inherited from the parent; no per-resource role bindings are possible.
| Resource | Parent | Can have bindings? |
|---|---|---|
| Organization | — | ✅ Yes |
| Workspace | Organization | ✅ Yes |
| Project | Workspace | ✅ Yes |
| Engine (Data Plane) | Workspace | ✅ Yes |
| Policy | Organization | ❌ Inherits from Org |
| User | Organization | ❌ Inherits from Org |
| Group | Organization | ❌ Inherits from Org |
| Model | Project | ❌ Inherits from Project |
| Connector | Project | ❌ Inherits from Project |
| Dataset | Project | ❌ Inherits from Project |
| Available Dataset | Project | ❌ Inherits from Project |
| Alert Rule | Model | ❌ Inherits from Project |
| Webhook | Workspace | ❌ Inherits from Workspace |
| Agent | Workspace | ❌ Inherits from Workspace |
| Custom Aggregation | Workspace | ❌ Inherits from Workspace |
| Data Plane Association | Engine | ❌ Inherits from Engine |
The Three Core Concepts
Permissions
A permission is the smallest unit of authorization — a single capability. Examples:
| Permission | What it allows |
|---|---|
organization_read | View org settings and metadata |
workspace_create_project | Create a new project in a workspace |
project_read | View a project and list its resources |
model_update | Edit a model's configuration |
model_delete | Delete a model |
dataset_read | View a dataset's metadata |
policy_create_alert_rule | Create an alert rule within a policy |
There are over 200 distinct permissions in the system, organized by the resource kind they apply to (organization, workspace, project, model, connector, dataset, policy, etc.).
Permissions are additive only — the system does not support "deny" rules. A user's effective access is the union of everything their roles grant them.
Roles
A role is a named bundle of permissions. Roles are the unit that admins actually work with — rather than granting dozens of individual permissions, you grant a role.
Roles have two important properties:
1. Where they can be bound — Each role is tagged as bindable at specific levels: org, workspace,
project, or data plane. A role can be bindable at multiple levels (e.g., Raw Data Reader can be bound
at org, workspace, or project). You can't accidentally bind a workspace role to a project.
2. Base roles — A role can inherit permissions from one or more "base roles." Project Admin lists
Project Reader as a base role, so any Project Admin automatically has all Project Reader permissions
too — without duplicating any permission definitions.
┌───────────────────────┐
│ Project Admin │
│ (own permissions: │
│ project_update, │
│ model_create, │
│ model_delete, ... │
└──────────┬────────────┘
│ inherits
▼
┌───────────────────────┐
│ Project Reader │
│ (permissions: │
│ project_read, │
│ model_read, │
│ model_list_alerts, │
│ dataset_read, ...) │
└───────────────────────┘
Role Bindings
A role binding is the connection between a subject (user or group) and a role at a specific resource. Think of it as: "Grant [subject] the [role] on [resource]."
Examples:
- "Grant Alice the
Project Readerrole on theFraud Detection v2project" - "Grant the
Data Science Teamgroup theWorkspace Adminrole on theProductionworkspace" - "Grant Bob the
Organization Readerrole on the Acme organization"
Bob ──────────────────────────────────────────────────┐
│
Data Science ─────────────────────────────────────┐ │
Team (group) │ │
▼ ▼
┌───────────────────────────────┐
│ Role Binding │
│ subject: Bob │
│ role: Project Reader │
│ resource: "Fraud v2" project │
└───────────────┬───────────────┘
│ grants access to
▼
┌───────────────────────────────┐
│ "Fraud v2" Project │
│ └── Model A │
│ └── Dataset B │
│ └── Connector C │
└───────────────────────────────┘
Role bindings are scoped — a binding to one project does not automatically grant access to other projects. Access must be explicitly granted, or granted via a higher-level binding that covers multiple resources (see the Cascading Roles section below).
Built-In Roles Reference
Arthur ships a set of built-in roles that cover the most common access patterns. Customers can also define custom roles. Built-in roles are organized by the level at which they can be bound.
Organization-Level Roles
| Role | Base Roles | Key Permissions |
|---|---|---|
| Organization Member | — | List workspaces, list users, view UI baseline |
| Organization Reader | — | Read org config, list workspaces/users/groups/roles/policies |
| Organization Admin | Org Reader | Write org config, manage users/groups/role bindings/policies |
| Raw Data Reader | — | Read raw dataset data (can also be bound at workspace or project) |
| Organization Read All | Org Reader + Workspace Read All | Read entire org tree (org + all workspaces + all projects) |
| Organization Super Admin | Org Admin + Workspace Super Admin | Full read/write access to everything in the org |
Workspace-Level Roles
| Role | Base Roles | Key Permissions |
|---|---|---|
| Workspace Reader | — | Read workspace config, list projects/engines/webhooks/agents |
| Governance Admin | — | View governance features, manage unregistered agents |
| Workspace Admin | Workspace Reader + Governance Admin | Write workspace config, create projects, manage webhooks/role bindings |
| Engine Manager | Workspace Reader | Create/update/delete engines |
| Custom Aggregation Manager | — | Create/manage custom aggregations |
| Workspace Read All | Workspace Reader + Project Reader | Read workspace + all its projects (cross-level cascade) |
| Workspace Super Admin | Workspace Read All + Workspace Admin + Project Admin + Engine Manager + Custom Aggregation Manager | Full access to workspace + all its projects |
Project-Level Roles
| Role | Base Roles | Key Permissions |
|---|---|---|
| Project Reader | — | Read project, list/read models/connectors/datasets/jobs, query metrics |
| Project Admin | Project Reader | Write project, create/update/delete models/connectors/datasets, manage role bindings |
| Raw Data Reader | — | Read raw dataset data within the project |
Special-Purpose Roles
| Role | Bindable At | Purpose |
|---|---|---|
| Data Plane Execution | Engine (Data Plane) | Granted to engine service accounts to allow job dequeuing |
Role Inheritance: How Base Roles Work
When a role lists other roles as "base roles," it automatically inherits all their permissions. This is recursive — base roles can themselves have base roles.
The Organization Super Admin role, for example, inherits from a deep tree:
Organization Super Admin
├── Organization Admin
│ └── Organization Reader (includes org_read, list_workspaces, etc.)
└── Workspace Super Admin
├── Workspace Read All
│ ├── Workspace Reader (includes workspace_read, list_projects, etc.)
│ └── Project Reader (includes project_read, model_read, etc.)
├── Workspace Admin
│ ├── Workspace Reader
│ └── Governance Admin
├── Project Admin
│ └── Project Reader
├── Engine Manager
│ └── Workspace Reader
└── Custom Aggregation Manager
When an Org Super Admin binding is created at the org level, the user gets every permission from every node in this tree. That's hundreds of individual permissions, delivered through a single role binding.
How Cascading Roles Work Across Levels
This is one of the most important and nuanced aspects of the authorization system.
The Core Problem
The resource hierarchy has four bindable levels: org, workspace, project, and engine. If you give
someone Workspace Admin, they can manage the workspace itself — but they cannot automatically read
or manage any of the projects inside it. Access to projects requires separate project-level bindings.
This is by design: it prevents accidental over-granting. A workspace admin managing configuration should not automatically see every project's models and data.
The Cascading Solution
Arthur includes a set of "super" roles specifically designed to bridge levels:
| Cascading Role | Bound At | Cascades Into |
|---|---|---|
| Workspace Read All | Workspace | All projects in that workspace |
| Workspace Super Admin | Workspace | All projects in that workspace |
| Organization Read All | Organization | All workspaces and all projects in the org |
| Organization Super Admin | Organization | All workspaces and all projects in the org |
When Workspace Read All is bound to a workspace, the system propagates project_read (and all other
Project Reader permissions) to every existing and future project in that workspace — without requiring
individual project bindings.
Visual: Non-Cascading vs. Cascading
Without cascading roles:
Workspace "Production" binding: Alice → Workspace Reader
│
├── Project "Fraud v2" ← Alice CANNOT access (no project binding)
│ ├── Model A
│ └── Dataset B
│
└── Project "Churn" ← Alice CANNOT access (no project binding)
└── Model C
Alice can see the workspace exists and its configuration, but cannot see any projects or their contents.
With cascading role (Workspace Read All):
Workspace "Production" binding: Alice → Workspace Read All
│
├── Project "Fraud v2" ← Alice CAN read (cascaded from workspace binding)
│ ├── Model A ← Alice CAN read
│ └── Dataset B ← Alice CAN read
│
└── Project "Churn" ← Alice CAN read (cascaded from workspace binding)
└── Model C ← Alice CAN read
One workspace binding covers every project now and in the future.
Mixed: some users targeted, one user broad:
Workspace "Production"
│ binding: Alice → Workspace Read All (Alice reads everything)
│ binding: Bob → Workspace Reader (Bob reads workspace config only)
│
├── Project "Fraud v2"
│ │ binding: Bob → Project Reader (Bob reads this one project)
│ ├── Model A
│ └── Dataset B
│
└── Project "Churn" ← Bob has NO access here
└── Model C
What Each Role Accesses at Each Level
This table shows the net effect of binding each role — what the subject can actually touch:
| Role | Org config | Workspace config | Project config | Models / Connectors / Datasets |
|---|---|---|---|---|
| Org Member | List only | List only | — | — |
| Org Reader | ✅ Read | List only | — | — |
| Org Admin | ✅ Read + Write | List only | — | — |
| Workspace Reader | — | ✅ Read | List only | — |
| Workspace Admin | — | ✅ Read + Write | List only | — |
| Project Reader | — | — | ✅ Read | ✅ Read |
| Project Admin | — | — | ✅ Read + Write | ✅ Read + Write |
| Workspace Read All | — | ✅ Read | ✅ Read (all projects) | ✅ Read (all projects) |
| Workspace Super Admin | — | ✅ Read + Write | ✅ Read + Write (all) | ✅ Read + Write (all) |
| Org Read All | ✅ Read | ✅ Read (all) | ✅ Read (all) | ✅ Read (all) |
| Org Super Admin | ✅ Read + Write | ✅ Read + Write (all) | ✅ Read + Write (all) | ✅ Read + Write (all) |
Bolded rows are the cascading roles. The others have a sharp boundary at their level.
Users, Groups, and How They Get Roles
Users
Users are authenticated via Arthur's identity provider (Keycloak). Once authenticated, their user ID is used to evaluate all authorization decisions.
A user gains access to resources by receiving role bindings — either directly (user-level binding) or indirectly via group membership.
Groups
Groups are collections of users. An admin creates a group, adds users to it, and then binds the group to a role. This is the recommended approach for managing access at scale: rather than giving each new team member individual bindings, you add them to the "Data Science Team" group and they inherit everything that group has been granted.
┌──────────────────────────────────────────────────────┐
│ Group: "Data Science Team" │
│ Members: Alice, Bob, Carol │
└──────────────────────────┬───────────────────────────┘
│ group has role binding
▼
┌─────────────────────────────┐
│ Role Binding │
│ role: Project Admin │
│ resource: "Fraud v2" proj │
└─────────────┬───────────────┘
│ grants
▼
┌─────────────────────────────┐
│ Alice, Bob, Carol │
│ all have Project Admin │
│ on "Fraud v2" │
└─────────────────────────────┘
Arthur supports two kinds of groups:
- Arthur-managed groups — created and maintained in Arthur itself
- IDP-managed groups — synced from an external identity provider (e.g., Okta, Azure AD). Membership is controlled outside Arthur, and Arthur trusts the IDP's group claims automatically.
Who Can Create Role Bindings?
Role binding creation is itself permission-controlled:
| Binding level | Permission required |
|---|---|
| Organization role binding | organization_create_role_binding |
| Workspace role binding | workspace_create_role_binding |
| Project role binding | project_create_role_binding |
These permissions are held by the Admin roles at each corresponding level.
Anti-privilege-escalation rule: A user cannot grant a role that contains permissions they don't already have. A Workspace Admin cannot create an Org Admin binding, because Org Admin contains permissions the Workspace Admin doesn't hold. The system validates this at the API level on every role binding creation.
Platform Bootstrapping and Resource Onboarding
How the Platform Bootstraps
When Arthur is first deployed, a bootstrapping sequence runs automatically:
Step 1: Load authorization schema
└── Defines all entity types and permission rules in SpiceDB
(the underlying authorization engine)
Step 2: Create built-in roles
└── All Arthur-managed roles are created in the database
and their permissions are registered in SpiceDB
(idempotent — safe to run multiple times)
Step 3: Create the first Organization
└── The initial org is set up, and the first user receives
Organization Super Admin — full access to bootstrap
the rest of the configuration
When a New Resource Is Created
Every time a resource is created, it is automatically registered with the authorization engine. No manual authorization setup is needed.
Admin creates Workspace "Production"
│
└── System automatically registers:
"Workspace 'Production' has parent Organization 'Acme'"
→ Org-level cascading roles now flow into this workspace
Admin creates Project "Fraud v2" in "Production"
│
└── System automatically registers:
"Project 'Fraud v2' has parent Workspace 'Production'"
→ Workspace-level cascading roles now flow into this project
Engineer creates Model "Fraud Classifier" in "Fraud v2"
│
└── System automatically registers:
"Model 'Fraud Classifier' has parent Project 'Fraud v2'"
→ Anyone with project_read can immediately read this model
The moment a resource is created under a parent, every user who already has cascading access to that parent gains access to the new resource automatically. No additional configuration required.
Onboarding a New Workspace: End-to-End
Here's a typical workflow when provisioning a workspace for a new team:
1. Org Admin creates workspace "Staging"
└── Workspace automatically linked to the org in auth system
2. Assign team lead:
Role Binding: [Carol] → [Workspace Super Admin] → [Workspace "Staging"]
└── Carol can now manage the workspace and all its future projects
3. Assign team (via group):
Role Binding: [ML Team group] → [Workspace Read All] → [Workspace "Staging"]
└── All ML Team members can read everything in any project in Staging
4. Team creates projects — no per-project setup needed
└── ML Team group access flows automatically into each new project
How a User Gains or Loses Access
Gaining Access
Path 1: Direct binding
Admin creates: [User] → [Role] → [Resource]
└── Immediate effect
Path 2: Group binding
Admin creates: [Group] → [Role] → [Resource]
└── All current and future group members gain access
Path 3: Cascading binding
Admin creates: [User/Group] → [Workspace Super Admin] → [Workspace]
└── Access automatically flows to all projects in that workspace
including projects created after the binding was set up
Losing Access
Path 1: Role binding deleted
└── Access revoked immediately
Path 2: User removed from group
└── If access came only via group, it is immediately revoked
(other bindings the user holds are unaffected)
Path 3: User deleted from organization
└── All of the user's role bindings are deleted
Because permissions are additive, a user retains access as long as at least one valid path exists. Revoking one binding does not remove access if another binding or group membership also grants it.
Auditing Access
At any time, an admin can:
- List role bindings for a user — see every role that user holds at every level
- List role bindings for a group — see what the group grants and where
- List role bindings for a resource — see all users and groups with access to an org, workspace, or project
Practical Scenarios
Scenario 1: Onboarding a New Data Scientist
Alice joins the Data Science team. The team already uses a group.
- Admin adds Alice to the "Data Science Team" group.
- The group already has a
Workspace Read Allbinding on "Production" andProject Adminbindings on three specific projects. - Alice immediately has read access to all Production projects plus admin access to the three specific projects — zero per-user setup required.
Scenario 2: Restricting Access to a Sensitive Project
Project X contains a model trained on sensitive PII data. Most of the team shouldn't see it.
- The team normally gets access via
Workspace Read Allon the "Production" workspace, which cascades to all projects. - To restrict Project X, the team's workspace binding is replaced with explicit per-project bindings that
exclude Project X. No one gets
Workspace Read Allthat would cascade in. - Only specifically designated users receive a
Project Readerbinding on Project X.
Alternatively: keep the workspace binding, but recognize that cascade roles are all-or-nothing within a workspace. If you need one project truly isolated, it typically lives in its own workspace where the team doesn't have cascading access.
Scenario 3: External Contractor Needs Temporary Read Access
A contractor needs to review models in one project for a compliance audit.
- Admin creates a
Project Readerbinding: [Contractor] → [Project Reader] → [Fraud v2 project]. - Contractor can read models, datasets, metrics — but nothing outside that project.
- When the audit is complete, the admin deletes the binding. Access is immediately revoked.
Scenario 4: Granting an Admin Broad Access
A new platform admin needs full access to everything.
- Admin grants
Organization Super Adminbinding at the org level. - Through role inheritance, the user now has every permission at every level — workspace, project, engine, and org config.
- No additional bindings needed.
Scenario 5: A New Model Gets Created
An engineer creates a new model inside an existing project.
- Engineer creates model "Fraud Classifier v3" inside the "Fraud v2" project.
- The authorization system automatically registers the model as a child of "Fraud v2".
- Every user who already had project read access to "Fraud v2" can immediately read the new model.
- No additional role bindings need to be created.
Key Design Principles
Additive-only — Permissions can only be granted, not denied. A user's access is the union of everything their role bindings grant. There are no negative rules to reason about.
Principle of least privilege — Targeted roles (like Workspace Reader) do not automatically
cascade. You must intentionally choose a cascading role. This makes it hard to accidentally over-grant.
No privilege escalation — An admin cannot grant a role that contains permissions they don't themselves hold. Enforced at the API level on every role binding creation.
Immediate consistency — Role binding changes take effect instantly. No caching delay.
Leaf resource simplicity — Models, connectors, datasets, and webhooks don't have their own role bindings. Their access is entirely determined by their parent project or workspace. This prevents permission fragmentation and makes it easy to reason about who can access a given resource.
New resources inherit automatically — Creating a resource under a parent automatically wires up inheritance. There is no authorization setup step when adding a model, connector, or dataset.
Updated 4 days ago