Skip to main content

Roles & Permissions

Crewship implements role-based access control at two levels: workspace roles (human users) and agent roles (AI agents within crews).

Workspace Roles (Human Users)

Users are assigned one of five roles within a workspace: Roles are defined as the OrgRole enum in the Prisma schema:

Permission Mapping (Code Implementation)

The canRole() function in Go (internal/api/helpers.go) maps roles to internal actions, grouped into three permission tiers:
In the current implementation, MEMBER and VIEWER have identical permissions (both map to read only). The distinction exists in the schema for future granularity (e.g., MEMBER may gain send_message or run_agent permissions).

Mutation routes declare their role at registration (complete mediation)

Every state-changing endpoint (POST/PUT/PATCH/DELETE) declares the role it requires at route registration, and a single middleware enforces that declaration before the handler runs. This is Saltzer & Schroeder complete mediation: authorization is a property of the route table, not a hand-placed check a new handler might forget. Workspace-scoped mutations register through authedMut(method, pattern, role, handler) (internal/api/rbac_routes.go), which records {method, pattern, role} into a walkable table and mounts the route behind RequireAuth → RequireWorkspace → requireRoleMW(role). The declared role is one of:
  • createMANAGER+ (the create/update tier). A MEMBER/VIEWER is refused 403.
  • manageADMIN+ (the manage/delete tier, e.g. workspace/member administration, feature flags, backups, approvals, hooks, GDPR data deletion).
  • self — self-scoped: the handler enforces ownership by user_id (own preferences, reactions, a saved view the caller owns, inbox items the caller can see).
  • inline — the handler runs a layered gate the middleware must not pre-empt: the role-or-capability gate (a MEMBER holding an explicit capability passes) or the per-agent owner / per-crew-elevation gate. The middleware guarantees workspace membership and passes through; the handler decides.
Control-plane mutations gated at MANAGER+ include crew-template deploy, crew policy, routine execution/control (run, replay, bulk_replay, signal, metadata, dry_run, test_run, tags, step overrides), checkpoints, agent / crew persona edits, and crew-composition suggestions.
Two tests make this a build-time invariant (internal/api/route_authz_invariant_test.go): TestEveryMutationRouteDeclaresRole walks the recorded route table and fails if any mutation route lacks a declared role, and TestNoLegacyAuthedMutationRegistration fails if a mutation route is registered through the old authed(...) chain instead of authedMut / authedSelfMut. Adding a new mutation route without declaring its role fails the build — the omission that previously slipped past review can no longer ship. The pre-existing inline requireRole / canRole checks are kept as belt-and-suspenders behind the middleware.
The X-Internal-Token sidecar surface and the public token/HMAC dispatch routes (webhooks, waitpoint callbacks, bootstrap/signup) are a separate trust boundary, each uniformly mediated by its own single wrapper, and are out of scope for the workspace-role invariant.

Signup Control

New user registration is controlled by the CREWSHIP_ALLOW_SIGNUP environment variable (or auth.allow_signup in YAML config). When disabled, only existing users can invite new members.

Agent Roles

Every AI agent has one of two functional roles:

AGENT

Individual contributor. Executes tasks, writes code, runs tools. The standard role for most agents.

LEAD

Crew orchestrator. Assigns tasks to agents, creates missions, monitors progress. Has access to the full sidecar API.

Agent Role Capabilities

The Equality Principle

Agent roles are functional, not hierarchical. A Lead is an equal colleague with orchestration responsibility — not a boss. This is a core Crewship philosophy.
The system prompt for each role reflects this equality: AGENT:
You are part of a crew on the Crewship — an expedition with a shared purpose that transcends any individual.
LEAD:
You are a crew member with orchestration responsibility. You are not a boss — you are an equal colleague who carries the soul and mission of the expedition to the whole team.

Internal API Authentication

The internal API (sidecar-to-crewshipd communication) uses token-based authentication:
This token is:
  1. Auto-generated cryptographically at startup if not configured
  2. Passed to sidecar processes via the IPC config
  3. Verified on every internal API request
Never hardcode the internal token. The auto-generation ensures each deployment gets a unique token. If you need to set it explicitly (e.g., for multi-server setups), use CREWSHIP_INTERNAL_TOKEN.

Container Security Boundaries

Agent processes run as UID 1001, while the sidecar runs as UID 1002. This prevents agents from:
  • Reading the sidecar’s credential store
  • Modifying network policy configuration
  • Accessing the IPC token
  • Tampering with the proxy’s authentication headers
See Container Isolation for details.

Credential Access Control

Credentials are protected by multiple layers:
  1. Workspace scoping: Credentials belong to a workspace
  2. Agent assignment: Credentials must be explicitly assigned to agents
  3. Encryption at rest: AES-256-GCM with versioned keys
  4. Sidecar injection: Agents never see raw credential values
  5. Keeper gating: L2+ credentials require AI evaluation
  6. Audit trail: All credential access is logged

JWT Authentication

User authentication uses JWT tokens signed with NEXTAUTH_SECRET:
  • WebSocket connections use short-lived tokens (ws_token_expiry, default 5 minutes)
  • API requests use session-based JWT tokens
  • Token validation is handled by the middleware layer