Skip to content

RBAC & Permissions Model

Starform uses a two-tier permission model with an optional environment protection flag. The design balances capability (beating Railway and Render on scoped access) with simplicity (avoiding enterprise-IAM complexity). The entire system is expressed in three database tables plus permission middleware in the Starbase API binary.

§15.1 Design Goals

  • Beat Railway on flexibility: environment-level write protection (Railway has project-scoped members but no environment-level protection)
  • Beat Render on cost: no per-seat pricing; unlimited team members on all paid plans
  • Support agencies and small teams: project-scoped access so a freelancer can see one project without seeing others in the workspace
  • Support finance roles without granting engineering access (Billing role)
  • Keep the model small enough to ship at MVP without enterprise-IAM complexity

§15.2 Hierarchy

flowchart TB
  classDef built fill:#3434DC22,stroke:#3434DC,color:#5B5EE8;
  classDef third fill:transparent,stroke:#808080,color:#808080;

  WS["Workspace"]
  WSM["Workspace Members<br/>(4 roles)"]
  BILL["Billing"]
  GS["Global Settings"]
  PROJ["Projects"]
  PM["Project Members<br/>(3 roles, scoped)"]
  PS["Project Settings"]
  ENV["Environments<br/>(is_protected flag)"]
  SVC["Services"]
  DB["Databases"]
  BUC["Buckets"]
  VG["Variable Groups"]

  WS --> WSM
  WS --> BILL
  WS --> GS
  WS --> PROJ
  PROJ --> PM
  PROJ --> PS
  PROJ --> ENV
  ENV --> SVC
  ENV --> DB
  ENV --> BUC
  ENV --> VG

  class WS,PROJ,ENV built;
  class WSM,BILL,GS,PM,PS,SVC,DB,BUC,VG third;
Diagram — RBAC hierarchy. Workspace → Projects → Environments, with member roles attached at the workspace and project tiers and the is_protected flag at the environment tier.

§15.3 Workspace Roles

Role Permissions
Owner Everything including billing, delete workspace, transfer ownership, manage all members
Admin Manage workspace members, create/delete projects, manage workspace settings. No billing access.
Billing View invoices, usage, spend breakdown per project, view payment methods (read-only). No project access.
Member Must be explicitly added to projects. No default project access. No billing visibility.

Rule: Workspace Owners and Admins have implicit access to all projects in the workspace with Project Admin role. Workspace Members have no project access unless explicitly added via project_members.

§15.4 Project Roles

Role Permissions
Admin Full project control. Manage project members. Can protect/unprotect environments. Can deploy to all environments including protected ones. Can delete services, databases, and the project itself.
Developer Can deploy to non-protected environments. Can edit env vars in non-protected environments. Can view logs, metrics, status across all environments. Cannot deploy or modify anything in protected environments. Cannot manage project members.
Viewer Read-only across all environments. Can see services, status, logs, metrics. Cannot deploy, edit, or modify anything. Cannot see secret values in any environment.

§15.5 Environment Protection

Any environment within a project can be marked as protected by a Project Admin. The canonical use case is production — marked protected to prevent accidental changes by Developers.

When an environment is protected:

  • ❌ Developers cannot deploy to it
  • ❌ Developers cannot edit env vars or secrets in that environment
  • ❌ Developers cannot delete resources in that environment
  • ✅ Developers can still view logs, metrics, and non-secret configuration
  • ✅ Developers can still view service status and deployment history

Who can deploy to protected environments: Workspace Owners, Workspace Admins, and Project Admins.

Default state: On project creation, production environment is created with is_protected = true. staging and dev are created unprotected. Project Admins can toggle the flag on any environment.

§15.6 Permission Resolution

When a user requests access to a project resource, permissions are resolved in this order:

  1. Is the user a Workspace Owner? → Full access (Project Admin role, can deploy to protected environments)
  2. Is the user a Workspace Admin? → Full access (Project Admin role, can deploy to protected environments)
  3. Is the user a Workspace Billing-only role? → No project access. Can only view billing pages.
  4. Is the user a Workspace Member with a direct Project Membership? → Use the Project role (Admin / Developer / Viewer). Apply environment protection rules.
  5. Is the user a Workspace Member without Project Membership? → No access to this project.
  6. Is the user not a Workspace Member? → No access.

This resolution happens once per request in permission middleware. The result is attached to the request context as a PermissionContext object:

PermissionContext · resolved once per request
type PermissionContext struct {
    UserID         string
    WorkspaceID    string
    WorkspaceRole  WorkspaceRole  // Owner | Admin | Billing | Member | None
    ProjectID      string          // Optional; set when request is project-scoped
    ProjectRole    ProjectRole     // Admin | Developer | Viewer | None
    EnvironmentID  string          // Optional; set when request is environment-scoped
    EnvProtected   bool            // True if the targeted environment is protected
}

Handlers check against this context rather than querying the database on every permission check.

§15.7 Database Schema (RBAC tables)

RBAC tables · Starbase Postgres schema
users (
    id UUID PRIMARY KEY,
    email TEXT UNIQUE NOT NULL,
    name TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

workspaces (
    id UUID PRIMARY KEY,
    name TEXT NOT NULL,
    slug TEXT UNIQUE NOT NULL,
    billing_email TEXT,
    plan TEXT DEFAULT 'hobby',
    created_at TIMESTAMPTZ DEFAULT NOW()
);

workspace_members (
    workspace_id UUID REFERENCES workspaces(id) ON DELETE CASCADE,
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    role TEXT CHECK (role IN ('owner', 'admin', 'billing', 'member')),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (workspace_id, user_id)
);

projects (
    id UUID PRIMARY KEY,
    workspace_id UUID REFERENCES workspaces(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    slug TEXT NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE (workspace_id, slug)
);

project_members (
    project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    role TEXT CHECK (role IN ('admin', 'developer', 'viewer')),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (project_id, user_id)
);

environments (
    id UUID PRIMARY KEY,
    project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    is_protected BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE (project_id, name)
);

Key schema decisions:

  • project_members only has rows for non-workspace-admin users with project access. Workspace Owners and Admins have implicit access (enforced in application logic, not stored as rows).
  • Billing role is at workspace level only; there is no concept of a billing role within a project.
  • Environment protection is a single boolean, not a per-user permission matrix. This keeps the model simple while covering 95% of real-world needs.

§15.8 API Enforcement

Every API endpoint in the Starbase API binary declares its required permission level. Middleware resolves the PermissionContext once and enforces the check before the handler runs.

Permission middleware flow:

  1. Extract user from session/JWT
  2. Load workspace membership for the request's target workspace
  3. If request is project-scoped, load project membership
  4. If request is environment-scoped, load environment protection status
  5. Build PermissionContext and attach to request
  6. Handler calls ctx.RequireWorkspaceAdmin(), ctx.RequireProjectDeveloper(), ctx.RequireEnvironmentWrite() etc. — helpers that return 403 if the check fails

§15.9 UI Implications

Projects view (Workspace level):

  • Each project card shows a team avatar stack indicating project members
  • Workspace Owners/Admins see all projects; Workspace Members see only projects they're directly added to
  • Billing role users see only a Billing page; no Projects view

Mission Control (Project level):

  • Breadcrumbs establish hierarchy (Projects / Acme SaaS Platform / production)
  • Protected environments show a lock icon in the environment switcher
  • Developers attempting to deploy to a protected environment see an inline explanation and a "Request Admin approval" CTA (feature deferred, but UI prepared)

Settings:

  • Workspace Settings (profile menu): Team members & roles, Billing, Workspace-wide API keys
  • Project Settings (sidebar): Project members, Environment variables, Custom domains, Integrations

§15.10 What to Build for MVP vs. Defer

Build now

  • Full two-tier schema (workspace_members + project_members tables)
  • Workspace roles: Owner, Admin, Billing, Member
  • Project roles: Admin, Developer, Viewer
  • Environment protection flag (single boolean)
  • Permission middleware
  • UI for workspace-level member management
  • UI for project-level member management
  • UI to toggle environment protection

Defer to v1.1

  • Custom roles
  • Per-user environment-level overrides ("Jane can deploy to prod but Bob can't")
  • Team/group abstractions
  • SSO/SAML integration (Enterprise tier)
  • Audit logs
  • "Request Admin approval" workflow for Developer deploys to protected environments

This foundation supports extension without restructuring: every deferred feature adds new tables or new columns, not new models.

§15.11 Competitive Positioning

Capability Railway Render Starform
Workspace roles 3 (Admin/Member/Deployer) 2 (Admin/Member) 4 (Owner/Admin/Billing/Member)
Project-scoped membership Yes (2 scopes) No Yes (3 roles)
Environment protection No Partial (destructive actions only) Yes (full write protection)
Per-seat pricing No Yes ($19/user Pro, $29/user Org) No
Billing-only role No Enterprise only All plans

This model ships with more capability than Railway and Render on day one, while remaining simpler than Vercel's Enterprise-gated team model.


Cross-references

Environment entity (RFC 1123, is_protected / is_ephemeral) → §24.1 · Var Groups inherit this protection model → §38.6 · enforced in the Starbase API binary · SSO (dashboard login, GitHub + Google) is distinct from the deferred customer auth primitive. Canonical map: Canonical Sources.