# Permissions & RBAC Audit — Design Document > **Date:** 2026-02-05 > **Status:** Draft > **Scope:** Full-stack permissions audit across trees, steps, sessions, teams, and admin features --- ## Background ResolutionFlow has a role-based access control system with three roles (`admin`, `engineer`, `viewer`), a `is_team_admin` boolean, and team-scoped resources. A comprehensive audit was performed across the entire codebase — frontend UX, backend architecture, and adversarial analysis — to assess the current state of permissions enforcement. **Audit methodology:** Three independent reviews were conducted: 1. **Frontend UX audit** — every page, component, API client, store, and router 2. **Backend architecture audit** — every endpoint, dependency, model, schema, and test 3. **Devil's advocate critique** — security gaps, edge cases, challenged assumptions --- ## Current State Summary ### What Works - **Session ownership** is properly enforced — users can only see/modify their own sessions - **Folder ownership** is properly scoped to the creating user - **Tree CRUD** has reasonable backend permission checks (engineer+ to create, author/admin/team-admin to edit, admin-only delete) - **Step Library** enforces visibility levels (private/team/public) and ownership for edit/delete - **Category/Tag management** is properly gated to admin and team admin roles - **Invite code management** is admin-only ### What's Broken The permission system has critical vulnerabilities, significant gaps, and inconsistencies across resource types. --- ## Findings by Severity ### CRITICAL #### 1. Self-Assignable Admin Role at Registration **Location:** `backend/app/schemas/user.py:14`, `backend/app/api/endpoints/auth.py:74-78` The `UserCreate` schema accepts a `role` field from client input. The registration endpoint passes it directly to the User model. Any user with a valid invite code can register with `role: "admin"` and gain full admin privileges. ```python # Current — VULNERABLE class UserCreate(UserBase): role: str = Field(default="engineer", ...) # Registration blindly trusts client input new_user = User(role=user_data.role, ...) ``` **Impact:** Complete authorization bypass. A single request escalates to admin. #### 2. XSS in HTML Export **Location:** `backend/app/api/endpoints/sessions.py:349-405` The HTML export function directly interpolates user-provided strings without escaping: ```python html.append(f'
Ticket: {session.ticket_number}
') html.append(f'Client: {session.client_name}
') ``` If any field contains `